Making use of yield in JavaScript
JavaScript's yield
keyword is relatively new, and the more I use it the more I find it useful.
The point of yield
is similar to using threads in other languages, in that it allows a function to stop execution until a later point in time.
A simple demonstration of that can be seen in using a simple generator function to count Fibonacci numbers starting at 0.
function* fibonacci() {
// Region A
yield 0;
// Region B
yield 1;
// Region C
let secondMostRecent = 0;
let mostRecent = 1;
// cap out somewhere for demo
while (mostRecent < 5) {
// Region X
const newValue = mostRecent + secondMostRecent;
yield newValue;
// Region Y
secondMostRecent = mostRecent;
mostRecent = newValue;
}
// Region D
return mostRecent + secondMostRecent;
}
// Region 1
const test = fibonacci();
// Region 2
console.log(test.next()); // { value: 0, done: false }
// Region 3
console.log(test.next()); // { value: 1, done: false }
// Region 4
console.log(test.next()); // { value: 1, done: false }
// Region 5
console.log(test.next()); // { value: 2, done: false }
// Region 6
console.log(test.next()); // { value: 3, done: false }
// Region 7
console.log(test.next()); // { value: 5, done: false }
// Region 8
console.log(test.next()); // { value: 8, done: true }
// Region 9
const allValues = [...fibonacci()];
// [0, 1, 1, 2, 3, 5]; only the values yielded are destructured
To understand what regions of code run at what point, I've annotated with some comments.
When a generator function is run, it does not initially run any of its code. Instead, when you call a generator function, it returns a generator; no code is executed until you call the .next()
method on that object.
When you call .next()
on the generator, the body of the generator function executes as normal until the first yield
keyword is used. Whatever value is passed to yield
will then become the value
property that is returned when you called .next()
on the generator. The generator will then pause, and not execute any other lines until .next()
is called again.
In our case, the following will be the order of our "regions":
- Region 1
- Region 2
- Region A
4. Yields, returning{ value: 0, done: false }
- Region 3
- Region B
6. Yields, returning{ value: 1, done: false }
- Region 4
- Region C
- Region X
10. Yields, returning{ value: 1, done: false }
- Region 5
- Region Y
- Region X
14. Yields, returning{ value: 2, done: false }
- Region 6
- Region Y
- Region X
18. Yields, returning{ value: 3, done: false }
- Region 7
- Region Y
- Region X
22. Yields, returning{ value: 5, done: false }
- Region 8
- Region Y
- Region D
26. Returns, causing next to have{ value: 8, done: true }
- Region 9
- (Repeat steps from for the destructure)
When we destructure, our second fibonacci()
generator is created, and our program waits for the fibonacci generator to run through the code until our done
status is true. Only the values from the yields, not the return, will be in the destructured array.