Generators are functions you can start, stop, and pause/resume that are controlled by an iterator. They can also be used to programmatically (and interactively, through yield/next() message passing) generate values to be consumed via iteration.

See the Pen thecodelog.com - ES6 Generators 1 by Deano (@deangilewicz) on CodePen.

A generator can pause itself in mid-execution, and can be resumed either right away or at a later time.

See the Pen thecodelog.com - ES6 Generators 2 by Deano (@deangilewicz) on CodePen.

When a paused yield expression is resumed, it’s completed/replaced by the resumption value.

See the Pen thecodelog.com - ES6 Generators 3 by Deano (@deangilewicz) on CodePen.

Yield is not just a pause point but an expression that sends out a value when pausing the generator. That first next() call is starting up the generator from its initial paused state, and running it to the first yield. Below when the first next() is invoked, there’s no yield expression waiting for a completion. If you passed a value to that first next() call, it would currently just be thrown away, because no yield is waiting to receive such a value.

See the Pen thecodelog.com - ES6 Generators 4 by Deano (@deangilewicz) on CodePen.

The argument you pass to next() completes whatever yield expression is currently paused waiting for a completion. Moreover, each pause/resume cycle in mid-execution is an opportunity for two-way message passing, where the generator can return a value, and the controlling code that resumes it can send a value back in.

See the Pen thecodelog.com - ES6 Generators 5 by Deano (@deangilewicz) on CodePen.

The yield expression will be completed by the value you resume the generator with.

See the Pen thecodelog.com - ES6 Generators 6 by Deano (@deangilewicz) on CodePen.

With yield, the completion value of the expression comes from resuming the generator with next(), the completion value of the yield * expression comes from the return value (if any) from the delegated-to iterator.

See the Pen thecodelog.com - ES6 Generator 7 by Deano (@deangilewicz) on CodePen.

Early Completion: The iterator attached to a generator supports the optional return() and throw() methods. Both of them have the effect of aborting a paused generator immediately.

See the Pen thecodelog.com - ES6 Generators 8 by Deano (@deangilewicz) on CodePen.

Once a generator is completed, either normally or early (as shown in previous example), it no longer processes any code or returns any values. A return is called automatically at the end of iteration by any of the ES6 constructs that consume iterators, such as the for..of loop and the spread operator. This notifies the generator if the controlling code is no longer going to iterate over it anymore, allowing any cleanup tasks to occur. Like normal function cleanup, the main way to accomplish this is to use a finally clause.

See the Pen thecodelog.com - ES6 Generators 9 by Deano (@deangilewicz) on CodePen.

You can use multiple iterators attached to the same generator concurrently:

See the Pen thecodelog.com - ES6 Generators 10 by Deano (@deangilewicz) on CodePen.

Early Abort: When you want to abort a generator you can use the throw method.

See the Pen thecodelog.com - ES6 Generators 11 by Deano (@deangilewicz) on CodePen.

Because the throw method basically injects a throw in replacement of the ‘yield 1’ line of the generator, and nothing handles this exception, it immediately propagates back out to the calling code, which handles it with a try..catch. Unlike the return method, the iterator’s throw method is never called automatically. Therefore, error handling can be expressed with a try..catch in both inbound and outbound directions.

See the Pen thecodelog.com - ES6 Generators 12 by Deano (@deangilewicz) on CodePen.

Errors can also propagate in both directions through yield * delegation:

See the Pen thecodelog.com - ES6 Generators 13 by Deano (@deangilewicz) on CodePen.

When *myFn10() calls ‘yield 1’, the 1 value passes through *myFn11() untouched. When *myFn10() calls ‘throw “myFn10: e2″‘, this error propagates to *myFn11() and is immediately caught by *myFn11()’s try..catch block. The error doesn’t pass through *myFn11() like the 1 value did. *myFn11()’s ‘catch’ then does a normal output of the ‘err (“myFn10: e2”)’ and then *myFn11() finishes normally, which is why the { value: undefined, done: true } iterator result comes back from it10.next(). If *myFn11() didn’t have a try..catch around the yield *.. expression, the error would of course propagate all the way out, and on the way through it still would complete (abort) *myFn11().