Generator Functions
A function declared with function* that returns a Generator object is called a generator function. That was the bookish definition of it 😉. Generators are functions that can be exited and re-entered. The context of the function is saved through all re-entries.
Execution
- When we call a generator function in JavaScript, it returns an iterator object.
- The iterator object has a
next()method. - Whenever
next()is called, that is when the generator body is executed. - The body is only executed till the first
yieldexpression. - If it is the last
yieldwe get the yielded value and adoneproperty to let us know that the generator has produced the last value.
Exception
- Instead of
yieldwe can also come acrossyield*. yield*delegates to another generator function.returnstatement in the generator will end the generator and so will an error in the generator.
Examples
function* numberGenerator() { // generator function declared with function*
let i = 0;
while (true) {
yield i++; // yields a generator
}
}
const numbers = numberGenerator();
console.log(numbers.next().value); // calling next on iterator produces next yield -> 0
console.log(numbers.next().value); // 1
console.log(numbers.next().value); // 2
console.log(numbers.next().value); // 3
The above example generates numbers as many time as we call next(). See how every execution of next() remembers the number to generate. The above example is relatively simple and explains the implementation of a generator function. Let us take another example that showcases done property.
function* numberGenerator() {
let i = 0;
while (i < 3) {
yield i++;
}
}
const numbers = numberGenerator();
console.log(numbers.next().value); // 0
console.log(numbers.next().value); // 1
console.log(numbers.next().value); // 2
console.log(numbers.next().done); // true
As you can see, when the value of i reaches 3, the generator sets the value of done property to true.
Examples for Exceptions
Delegating to another function
function* evenNumberGenerator() {
yield 4;
yield 8;
}
function* numberGenerator() {
yield 1;
yield* evenNumberGenerator();
yield 33;
}
const numbers = numberGenerator();
console.log(numbers.next().value); // 1
console.log(numbers.next().value); // 4
console.log(numbers.next().value); // 8
console.log(numbers.next().value); // 33
When we call numberGenerator for the first time, the first yield returns 1. The next two yields are handled by evenNumberGenerator with values 4 and 8. Finally, we receive 33 from numberGenerator.
Generator with return
function* numberGenerator() {
yield 1;
return "no more numbers"
yield 33;
}
const numbers = numberGenerator();
console.log(numbers.next().value); // 1
console.log(numbers.next()); // { value: 'no more numbers', done: true }
console.log(numbers.next().value); // undefined
numberGenerator yields 1 on the first next() but marks generator as done on the second iteration (as soon as a return occurs). It does provide the value from the return statement.
Passing arguments
Generator functions are capable of taking arguments as regular functions.
function* numberGenerator() {
console.log(0);
console.log(1, yield);
console.log(2, yield);
console.log(3, yield);
}
const numbers = numberGenerator();
numbers.next(); // 0
numbers.next("uno"); // 1 "uno"
numbers.next("two"); // 2 "two"
numbers.next("tres"); // 3 "tres"
Keep in mind that the very first next() generates a 0. It is caused by the first console log statement in numberGenerator. As I mentioned before, when we call next() on a generator, the body of the function is executed till the first yield.