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 start
1function *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 first yield and nothing receives its argument. Watch tick print before any value goes in.
  • why it mattersnext(value) sending data back to the producer is a preview of how a stream reader pushes back on its writer.
  • why it mattersthrow and return unwind the stack and run finally blocks. That is how a consumer can stop a producer early, which streams build on for cancellation.