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.

Chrome 146 required for native 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