Generators in ES2015
Generators are a new feature provided by the ES2015 Language Specification. A generator is a method that can be exited and re-entered multiple times without losing state. The examples below will step through creating basic and complex generators.
Basic Generator
A generator must be declared using function* and contain at least one yield statement.
function* newIterator() {
yield 1;
yield 2;
}
Calling a generator method does not execute the body but instead returns a Generator object.
function* newIterator() {
...
}
const iter = newIterator();
The Generator object supports the iterable and iterator protocols. Calling the object’s next method will execute the function until the first yield statement. The expression following the yield will be returned. The following iterations will continue from the previous yield statement and execute until the next yield statement.
In the following example, iterating over the generator will produce 1 and then 2.
function* newIterator() {
yield 1;
yield 2;
}
const iter = newIterator();
console.log(iter.next()); // prints { value: 1, done: false }
console.log(iter.next()); // prints { value: 2, done: false }
console.log(iter.next()); // prints { value: undefined, done: true }
Complex Generators
Internal State
Generators have the ability to maintain state within the function. This allows for complex algorithms that can produce results on demand.
In the following example, the generator function uses a for loop to increment the state of i. The internal state enables the function return the new value of i for each iteration.
function* newIterator() {
for (let i = 0; i < 2; i++) {
yield i;
}
}
const iter = newIterator();
console.log(iter.next()); // prints { value: 1, done: false }
console.log(iter.next()); // prints { value: 2, done: false }
console.log(iter.next()); // prints { value: undefined, done: true }
Modification of Internal State
The internal state of a generator can be modified by passing a value into the iterator’s next method. Any value passed into the next method will be returned from the yield statement.
In the following example, a boolean flag is passed into the iterator’s next method to reset the count.
function* newIterator() {
for (let i = 0; i < 3; i++) {
const reset = yield i;
if (reset) {
i = -1; // reset for loop
}
}
}
const iter = newIterator();
console.log(iter.next()); // prints { value: 0, done: false }
console.log(iter.next()); // prints { value: 1, done: false }
console.log(iter.next(true)); // prints { value: 0, done: false }
console.log(iter.next()); // prints { value: 1, done: false }
console.log(iter.next()); // prints { value: 2, done: false }
console.log(iter.next()); // prints { value: undefined, done: true }
Future
Generators appear to be a fundamental part of the language moving forward. The Async-Await proposal for ES7 leverages generators to improve the language’s support for writing asynchronous JavaScript.
As of this post, generators are supported in nodejs and in some major browsers. Unlike most ES6 features, babel’s polyfill must be used instead of the transpiler. As with most polyfills, tread carefully when adding any dependency to your code.