02 · plural · spatial · setter
Generator functions: a conversation in both directions
A generator function writes the lazy
range as plain procedural code. Its iterator gets more than next(): the consumer can pass a value back in as the result of yield, throw an error in at the paused yield, or return early. Information flows forward as values and backward as requests.Calling a generator function does not execute the function, but instead sets up a state machine to track where we are in the function and returns an iterator.
The generator function
suspended start1function *echo() {2 var message;3 while (true) {4 console.log("tick");5 message = yield message;6 console.log("tock");7 }8}
ran during the last callsuspended at this yield
The iterator: you drive it
first call primes it: no yield to receive a value yet
Calls and the iterations they returned
Calling the generator function ran nothing. It only built the state machine.
console
No output yet.
Clarity restored
The generator version of range reads like the eager array maker but behaves like the lazy iterator. Each next() resumes the function until the next yield.
function *range(start, stop, step) {
while (start < stop) {
yield start;
start += step;
}
}
var iterator = range(0, Infinity, 1);
expect(iterator.next().value).toBe(0);
expect(iterator.next().value).toBe(1);
expect(iterator.next().value).toBe(2);Java iterators have hasNext(), but a generator can't: knowing whether more values exist would mean running arbitrary code ahead of time, which is the Halting Problem. The iterator has to ask for a value before the generator can say it has none.
The array generator: yield as a method
If an array iterator consumes an array, an array generator would produce one. It's of dubious use on its own, but it sets up the interface asynchronous generators need: yield, return and throw as methods on the setter side.
var array = [];
var generator = generate(array);
generator.yield(10);
generator.yield(20);
generator.yield(30);
expect(array).toEqual([10, 20, 30]);array = []
- why it mattersThe first
next()only primes the generator: it runs to the firstyieldand nothing receives its argument. Watchtickprint before any value goes in. - why it matters
next(value)sending data back to the producer is a preview of how a stream reader pushes back on its writer. - why it matters
throwandreturnunwind the stack and runfinallyblocks. That is how a consumer can stop a producer early, which streams build on for cancellation.