Iterators and Generators
The following article is meant to discuss about Iterators, Generators, Iterable objects and Yield keyword as provided in ECMAScript 6. For the in-depth explanation please see MDN.
Iterable Objects
Iterable objects are a generalization of arrays. That’s a concept that allows to make any object usable in a for..of loop.
Arrays by themselves are iterable. But not only arrays. Strings are iterable too, and many other built-in objects as well.
Iterables are widely used by the core JavaScript. As we’ll see many built-in operators and methods rely on them.
Iterating over an Array
let iterable = [10, 20, 30];
for (let value of iterable) {
value += 1;
console.log(value);
}
// 11
// 21
// 31Built-in iterables
String, Array, TypedArray, Map and Set are all built-in iterables, because each of their prototype objects implements an @@iterator method.
User-defined iterables
We can make our own iterables like:
let range = {
from: 1,
to: 5
};To make the range iterable (and thus let for..of work) we need to add a method to the object named Symbol.iterator (a special built-in symbol just for that).
range[Symbol.iterator] = function() {
//it should return an iterator object which has a next() function
}- When
for..ofstarts, it calls that method (or errors if not found). - The method must return an iterator — an object with the method
next. - When
for..ofwants the next value, it callsnext()on that object. - The result of
next()must have the form{done: Boolean, value: any}, wheredone = truemeans that the iteration is finished, otherwisevaluemust be the new value.
Here’s the full implementation for range:
let range = {
from: 1,
to: 5
};// 1. call to for..of initially calls this
range[Symbol.iterator] = function() { // 2. ...it returns the iterator:
return {
current: this.from,
last: this.to, // 3. next() is called on each iteration by the for..of loop
next() {
// 4. it should return the value as an object {done:.., value :...}
if (this.current <= this.last) {
return { done: false, value: this.current++ };
} else {
return { done: true };
}
}
};
};// now it works!
for (let num of range) {
alert(num); // 1, then 2, 3, 4, 5
}
Note:
The
rangeitself does not have thenext()method.Instead, another object, a so-called “iterator” is created by the call to
range[Symbol.iterator](), and it handles the iteration.
Generators
Generators are functions which can be exited and later re-entered. Their context (variable bindings) will be saved across re-entrances.
Generator functions allow you to define an iterative algorithm by writing a single function whose execution is not continuous (by using yield keyword)
Generator functions are written using the
function*syntax.
Example of a simple generator function:
function* foo(){
yield 1;
yield 2;
}
var gen = foo();
console.log(gen.next()); // {value: 1, done: false}Calling a generator function does not execute its body immediately; an iterator object for the function is returned instead.
Hence doing following causes error:
> foo.next(); // Uncaught TypeError: foo.next is not a functionWhen the iterator’s next() method is called, the generator function's body is executed until the first yield expression, which specifies the value to be returned from the iterator.
The next() method returns an object with a value property containing the yielded value and a done property which indicates whether the generator has yielded its last value as a boolean.
For the above snippet doneproperty results to true after all yields have finished:
> gen.next()
// {value: 1, done: false}
> gen.next()
//{value: 2, done: false}
> gen.next()
//{value: undefined, done: true}Calling the next() method with an argument(if required by generator function) will resume the generator function execution, replacing the yieldexpression where execution was paused with the argument from next().
A return statement in a generator, when executed, will make the generator finished (i.e the done property of the object returned by it will be set to true). If a value is returned, it will be set as the value property of the object returned by the generator.
Read: Generator without yield
Yield
The
yieldkeyword is used to pause and resume a generator function
The yield keyword pauses generator function execution and the value of the expression following the yield keyword is returned to the generator's caller. It can be thought of as a generator-based version of the return keyword.
The yield keyword actually returns an IteratorResult object with two properties, value and done. The value property is the result of evaluating the yield expression, and done is false, indicating that the generator function has not fully completed.
- A
yield, which causes the generator to once again pause and return the generator's new value. The next timenext()is called, execution resumes with the statement immediately after theyield. throwis used to throw an exception from the generator. This halts execution of the generator entirely, and execution resumes in the caller as is normally the case when an exception is thrown.- The end of the generator function is reached; in this case, execution of the generator ends and an
IteratorResultis returned to the caller in which thevalueisundefinedanddoneistrue.
> gen.next()
//{value: undefined, done: true}- A
returnstatement is reached. In this case, execution of the generator ends and anIteratorResultis returned to the caller in which thevalueis the value specified by thereturnstatement anddoneistrue.
Example : Using Generator to define our user-defined iterables:
var myIterable = {};
myIterable[Symbol.iterator] = function* () {
yield 1;
yield 2;
yield 3;
};
[...myIterable]; // [1, 2, 3][…IteratorObj] is a Spread syntax which uses the same iteration protocol under the hood.
