v146 · JavaScript · Practical Patterns
Practical Patterns
Four real-world patterns around Iterator.concat() and iterator helpers: merging search results from multiple sources, processing heterogeneous data collections, building lazy pipelines that stop early when enough results are found, and interleaving timestamp-sorted log streams with a small merge helper.
Iterator.concat(). Older browsers use a generator polyfill so all patterns still run.
pattern 1: merging search results
Federated search across sources
Merge results from local storage, recent items, and remote suggestions into a single ordered sequence. Stop after taking the first 5 matches.
// Three data sources (synchronous iterables)
const localResults = ['Local: apple pie', 'Local: apple cider'];
const recentItems = ['Recent: apple watch', 'Recent: apple store'];
function* suggestions() {
yield 'Suggest: apple tv';
yield 'Suggest: apple music';
yield 'Suggest: apple park';
}
// Merge lazily — take the first 5 across all sources
const results = Iterator.concat(localResults, recentItems, suggestions())
.filter(r => r.toLowerCase().includes('apple'))
.take(5)
.toArray();
// Sources are consumed in order; generation stops as soon as we have 5
pattern 2: heterogeneous data processing
Process items from different collections uniformly
A Set of active users, a Map of admin users, and an array of guest users — all processed through one pipeline without flattening into a common format first.
const activeUsers = new Set(['alice', 'bob', 'carol']);
const adminUsers = new Map([['dave', 'admin'], ['eve', 'superadmin']]);
const guestIds = ['guest-001', 'guest-002'];
// Extract usernames from each source
const allNames = Iterator.concat(
activeUsers, // strings directly
adminUsers.keys(), // Map keys
guestIds
);
const report = allNames
.map(name => ({ name, display: name.replace('-', ' #') }))
.filter(u => !u.name.startsWith('guest'))
.toArray();
pattern 3: lazy pipeline with early exit
Find first N matching items across large collections
Three "large" arrays (simulated), looking for prime numbers. Iterator.concat().filter().take() stops reading from source arrays as soon as enough primes are found — no need to process the rest.
function isPrime(n) {
if (n < 2) return false;
for (let i = 2; i <= Math.sqrt(n); i++) {
if (n % i === 0) return false;
}
return true;
}
// Three source ranges with pull counters
const counts = { rangeA: 0, rangeB: 0, rangeC: 0 };
function* trackedRange(name, start, length) {
for (let i = 0; i < length; i++) {
counts[name]++;
yield start + i;
}
}
// Takes only what it needs from the concatenated sequence
const firstEight = Iterator.concat(
trackedRange('rangeA', 1, 50),
trackedRange('rangeB', 51, 50),
trackedRange('rangeC', 101, 50)
)
.filter(isPrime)
.take(8)
.toArray();
// Pull counts: rangeA 19/50, rangeB 0/50, rangeC 0/50
pattern 4: merge sorted log streams
Interleave timestamp-sorted sources
Each source is already sorted by timestamp. A merge helper keeps one lookahead value from each iterator and yields the next earliest log, so the output is chronological instead of "all edge, then all auth, then all worker".
function* mergeSortedLogs(...sources) {
const cursors = sources.map((source) => {
const iterator = source[Symbol.iterator]();
const first = iterator.next();
return { iterator, current: first.done ? null : first.value };
});
while (true) {
let winner = null;
for (const cursor of cursors) {
if (!cursor.current) continue;
if (!winner || cursor.current.time < winner.current.time) {
winner = cursor;
}
}
if (!winner) return;
yield winner.current;
const next = winner.iterator.next();
winner.current = next.done ? null : next.value;
}
}
const merged = [...mergeSortedLogs(edgeLogs, authLogs, workerLogs)];
// Output interleaves edge/auth/worker by timestamp, not source order.
see also
- Iterator.concat() Demo — basic examples with different iterable types
- ChromeStatus entry
- MDN — Iterator.concat()
- TC39 proposal