Alexander Lin
Alexander Lin

Reputation: 33

How to sequentially chain two sync iterators in JavaScript?

Let's say we have two async iterators,

const asyncIterable1 = {
  [Symbol.asyncIterator]() {
    return {
      i: 0,
      next() {
      if (this.i < 3) {
        return Promise.resolve({ value: this.i++, done: false });
      }

      return Promise.resolve({ done: true });
    }
  };
 }
};

const asyncIterable2 = {
  [Symbol.asyncIterator]() {
    return {
      i: 3,
      next() {
      if (this.i < 5) {
        return Promise.resolve({ value: this.i++, done: false });
      }

      return Promise.resolve({ done: true });
    }
  };
 }
};

Now, is there a way to combine these two iterators into one iterator that would return a sequence of 0,1,2 and then 3,4?

Upvotes: 3

Views: 1491

Answers (3)

trincot
trincot

Reputation: 350961

Another alternative is to use the iterator helper method flatMap, introduced with ECMAScript 2025:

const iterator1 = (function* () { yield 1; yield 2; })();
const iterator2 = (function* () { yield 3; yield 4; })();

const iterator1then2 = [iterator1, iterator2].values().flatMap(Object);
console.log(...iterator1then2);

NB: Object is used here as an identity function.

Upvotes: 0

Jonas Wilms
Jonas Wilms

Reputation: 138477

Yeah, I'd use yield* for that:

    const combine = (a, b) => (function* () { yield* a; yield* b; })();
    
    const iterator = combine(
      (function* () { yield 1; yield 2; })(),
      (function* () { yield 3; yield 4; })()
    );
    
    console.log(iterator.next(), iterator.next(), iterator.next(), iterator.next(), iterator.next());

This works analogously for async iterators. You'll loose the return value ("the done yield") of the first iterator though. You could capture it however (the value yield* evaluates to).

For sure if you're among the people that like to reinvent wheels, you can also implement such functionality "by hand" without generator functions:

function combine(...iterators) {
  let pos = 0, iterator = iterators[pos];
  return {
    next() {
      let result = { done: true };
      do {
        result = iterator.next();
        if(!result.done) break;
        iterator = iterators[ pos++ ];
      } while(iterator)
          
      return result;
    }
  };
}

 const iterator = combine(
          (function* () { yield 1; yield 2; })(),
          (function* () { yield 3; yield 4; })()
 );
        
 console.log(iterator.next(), iterator.next(), iterator.next(), iterator.next(), iterator.next());

Upvotes: 4

Gershom Maes
Gershom Maes

Reputation: 8170

In addition to Jonas' great answer, we could generalize a bit further and combine an arbitrary number of iterators:

let combine = function*(...iterators) {
  for (let it of iterators) yield* it;
};

Upvotes: 7

Related Questions