Sammy I.
Sammy I.

Reputation: 2725

Do Generators return both an iterator and an iterable?

From this question I asked before I learned that for...of expects an iterable, which is

an object that implements the @@iterable method, meaning that the object (or one of the objects up its prototype chain) must have a property with a @@iterator key which is available via constant Symbol.iterator

So, an iterable would look something like this:

const iterableObject: {
  [Symbol.iterator]: function someIteratorFunction() {
    //...
  }
}

On the other hand we have generators, which look like this:

function* generatorFoo(){
  yield 1;
  yield 2;
  yield 3;
  yield 4;
}

And can be used in for...of constructs like this:

for(const item of generatorFoo())
  console.log(item);

So, calling generatorFoo() returns an iterable since for...of has no problem dealing with it. I can also call generatorFoo()[Symbol.iterator]() and receive an iterator, to confirm this.

However, calling generatorFoo would also return an iterator since I can call generatorFoo() I get an object with a method next to get the next value of the iterator, like so:

const iteratorFromGen = generatorFoo();
iteratorFromGen.next().value; //1
iteratorFromGen.next().value; //2
iteratorFromGen.next().value; //3
iteratorFromGen.next().value; //4

Does this mean that calling a generator function returns an object that has access to a next method and a [Symbol.iterator] method?

When calling a generator function, do we get an object that is both an iterator and an iterable? How is this accomplished?

Upvotes: 3

Views: 404

Answers (1)

CRice
CRice

Reputation: 32146

In short, yes, a generator is both an iterable and an iterator.

From MDN:

The Generator object is returned by a generator function and it conforms to both the iterable protocol and the iterator protocol.

The iterator returned by calling the Symbol.iterator method of the returned generator object is the same as the generator object itself. For example:

function* gen() {
  let i = 0;
  while (i < 10) yield i++;
}

const iterableIterator = gen();
console.log(iterableIterator === iterableIterator[Symbol.iterator]()); // true

You can even replicate that pattern pretty easily by conforming to the iterator protocol, and having a Symbol.iterator method that returns this. So for example:

class MyIterableIterator {
  
  constructor(arr) {
    this.arr = arr;
    this.i = 0;
  }

  [Symbol.iterator]() {
    return this;
  }
  
  next() {
    if (this.i === this.arr.length) return {done: true}
    return {value: this.arr[this.i++], done: false}
  }
}

const iterableIterator = new MyIterableIterator([1, 2, 3, 4, 5]);
console.log(iterableIterator === iterableIterator[Symbol.iterator]()); // true

// Works fine if you call next manually, or using a for-of loop.
console.log(iterableIterator.next())
for (let item of iterableIterator) {
  console.log(item);
}

Upvotes: 4

Related Questions