Reputation: 2725
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 constantSymbol.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
Reputation: 32146
In short, yes, a generator is both an iterable and an iterator.
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