What Are Generators?
A generator is a function that returns an iterator. Generator functions are indicated by a star character (*
) after the function
keyword and use the new yield
keyword. It doesn’t matter if the star is directly next to function
or if there’s some whitespace between it and the *
character, as in this example:
// generator
function *createIterator() {
yield 1;
yield 2;
yield 3;
}
// generators are called like regular functions but return an iterator
let iterator = createIterator();
console.log(iterator.next().value); // 1
console.log(iterator.next().value); // 2
console.log(iterator.next().value); // 3
The *
before createIterator()
makes this function a generator. The yield
keyword, also new to ECMAScript 6, specifies values the resulting iterator should return when next()
is called, in the order they should be returned. The iterator generated in this example has three different values to return on successive calls to the next()
method: first 1
, then 2
, and finally 3
. A generator gets called like any other function, as shown when iterator
is created.
Perhaps the most interesting aspect of generator functions is that they stop execution after each yield
statement. For instance, after yield 1
executes in this code, the function doesn’t execute anything else until the iterator’s next()
method is called. At that point, yield 2
executes. This ability to stop execution in the middle of a function is extremely powerful and leads to some interesting uses of generator functions (discussed in the “Advanced Iterator Functionality” section).
The yield
keyword can be used with any value or expression, so you can write generator functions that add items to iterators without just listing the items one by one. For example, here’s one way you could use yield
inside a for
loop:
function *createIterator(items) {
for (let i = 0; i < items.length; i++) {
yield items[i];
}
}
let iterator = createIterator([1, 2, 3]);
console.log(iterator.next()); // "{ value: 1, done: false }"
console.log(iterator.next()); // "{ value: 2, done: false }"
console.log(iterator.next()); // "{ value: 3, done: false }"
console.log(iterator.next()); // "{ value: undefined, done: true }"
// for all further calls
console.log(iterator.next()); // "{ value: undefined, done: true }"
This example passes an array called items
to the createIterator()
generator function. Inside the function, a for
loop yields the elements from the array into the iterator as the loop progresses. Each time yield
is encountered, the loop stops, and each time next()
is called on iterator
, the loop picks up with the next yield
statement.
Generator functions are an important feature of ECMAScript 6, and since they are just functions, they can be used in all the same places. The rest of this section focuses on other useful ways to write generators.
W> The yield
keyword can only be used inside of generators. Use of yield
anywhere else is a syntax error, including functions that are inside of generators, such as: W> W> js W> function *createIterator(items) { W> W> items.forEach(function(item) { W> W> // syntax error W> yield item + 1; W> }); W> } W>
W> W> Even though yield
is technically inside of createIterator()
, this code is a syntax error because yield
cannot cross function boundaries. In this way, yield
is similar to return
, in that a nested function cannot return a value for its containing function.
Generator Function Expressions
You can use function expressions to create generators by just including a star (*
) character between the function
keyword and the opening parenthesis. For example:
let createIterator = function *(items) {
for (let i = 0; i < items.length; i++) {
yield items[i];
}
};
let iterator = createIterator([1, 2, 3]);
console.log(iterator.next()); // "{ value: 1, done: false }"
console.log(iterator.next()); // "{ value: 2, done: false }"
console.log(iterator.next()); // "{ value: 3, done: false }"
console.log(iterator.next()); // "{ value: undefined, done: true }"
// for all further calls
console.log(iterator.next()); // "{ value: undefined, done: true }"
In this code, createIterator()
is a generator function expression instead of a function declaration. The asterisk goes between the function
keyword and the opening parentheses because the function expression is anonymous. Otherwise, this example is the same as the previous version of the createIterator()
function, which also used a for
loop.
I> Creating an arrow function that is also a generator is not possible.
Generator Object Methods
Because generators are just functions, they can be added to objects, too. For example, you can make a generator in an ECMAScript 5-style object literal with a function expression:
var o = {
createIterator: function *(items) {
for (let i = 0; i < items.length; i++) {
yield items[i];
}
}
};
let iterator = o.createIterator([1, 2, 3]);
You can also use the ECMAScript 6 method shorthand by prepending the method name with a star (*
):
var o = {
*createIterator(items) {
for (let i = 0; i < items.length; i++) {
yield items[i];
}
}
};
let iterator = o.createIterator([1, 2, 3]);
These examples are functionally equivalent to the example in the “Generator Function Expressions” section; they just use different syntax. In the shorthand version, because the createIterator()
method is defined with no function
keyword, the star is placed immediately before the method name, though you can leave whitespace between the star and the method name.