ES6 In Depth: Generators

ES6 In Depth is a series on new features being added to the JavaScript programming language in the 6th Edition of the ECMAScript standard, ES6 for short.

I’m excited about today’s post. Today, we’re going to discuss the most magical feature in ES6.

What do I mean by “magical”? For starters, this feature is so different from things that already existed in JS that it may seem completely arcane at first. In a sense, it turns the normal behavior of the language inside out! If that’s not magic, I don’t know what is.

Not only that: this feature’s power to simplify code and straighten out “callback hell” borders on the supernatural.

Am I laying it on a bit thick? Let’s dive in and you can judge for yourself.

Introducing ES6 generators

What are generators?

Let’s start by looking at one.

<pre>
function* quips(name) {
yield "hello " + name + "!";
yield "i hope you are enjoying the blog posts";
if (name.startsWith("X")) {
yield "it's cool how your name starts with X, " + name;
}
yield "see you later!";
}
</pre>

This is some code for a talking cat, possibly the most important kind of application on the Internet today. (Go ahead, click the link, play with the cat. When you’re thoroughly confused, come back here for the explanation.)

It looks sort of like a function, right? This is called a generator-function and it has a lot in common with functions. But you can see two differences right away:

  • Regular functions start with function. Generator-functions start with function*.

  • Inside a generator-function, yield is a keyword, with syntax rather like return. The difference is that while a function (even a generator-function) can only return once, a generator-function can yield any number of times. The yield expression suspends execution of the generator so it can be resumed again later.

So that’s it, that’s the big difference between regular functions and generator-functions. Regular functions can’t pause themselves. Generator-functions can.

What generators do

What happens when you call the quips() generator-function?

<pre>
> var iter = quips("jorendorff");
[object Generator]
> iter.next()
{ value: "hello jorendorff!", done: false }
> iter.next()
{ value: "i hope you are enjoying the blog posts", done: false }
> iter.next()
{ value: "see you later!", done: false }
> iter.next()
{ value: undefined, done: true }
</pre>

You’re probably very used to ordinary functions and how they behave. When you call them, they start running right away, and they run until they either return or throw. All this is second nature to any JS programmer.

Calling a generator looks just the same: quips("jorendorff"). But when you call a generator, it doesn’t start running yet. Instead, it returns a paused Generator object (called iter in the example above). You can think of this Generator object as a function call, frozen in time. Specifically, it’s frozen right at the top of the generator-function, just before running its first line of code.

Each time you call the Generator object’s .next() method, the function call thaws itself out and runs until it reaches the next yield expression.

That’s why each time we called iter.next() above, we got a different string value. Those are the values produced by the yield expressions in the body of quips().

On the last iter.next() call, we finally reached the end of the generator-function, so the .done field of the result is true. Reaching the end of a function is just like returning undefined, and that’s why the .value field of the result is undefined.

Now might be a good time to go back to the talking cat demo page and really play around with the code. Try putting a yield inside a loop. What happens?

In technical terms, each time a generator yields, its stack frame—the local variables, arguments, temporary values, and the current position of execution within the generator body—is removed from the stack. However, the Generator object keeps a reference to (or copy of) this stack frame, so that a later .next() call can reactivate it and continue execution.

It’s worth pointing out that generators are not threads. In languages with threads, multiple pieces of code can run at the same time, usually leading to race conditions, nondeterminism, and sweet sweet performance. Generators are not like that at all. When a generator runs, it runs in the same thread as the caller. The order of execution is sequential and deterministic, and never concurrent. Unlike system threads, a generator is only ever suspended at points marked by yield in its body.

All right. We know what generators are. We’ve seen a generator run, pause itself, then resume execution. Now for the big question. How could this weird ability possibly be useful?

Generators are iterators

Last week, we saw that ES6 iterators are not just a single built-in class. They’re an extension point of the language. You can create your own iterators just by implementing two methods: [Symbol.iterator]() and .next().

But implementing an interface is always at least a little work. Let’s see what an iterator implementation looks like in practice. As an example, let’s make a simple range iterator that simply counts up from one number to another, like an old-fashioned C for (;;) loop.

<pre>
// This should "ding" three times
for (var value of range(0, 3)) {
alert("Ding! at floor #" + value);
}
</pre>

Here’s one solution, using an ES6 class. (If the class syntax is not completely clear, don’t worry—we’ll cover it in a future blog post.)

<pre>
class RangeIterator {
constructor(start, stop) {
this.value = start;
this.stop = stop;
}

[Symbol.iterator]() { return this; }

next() {
var value = this.value;
if (value < this.stop) {
this.value++;
return {done: false, value: value};
} else {
return {done: true, value: undefined};
}
}
}

// Return a new iterator that counts up from 'start' to 'stop'.
function range(start, stop) {
return new RangeIterator(start, stop);
}
</pre>

See this code in action.

This is what implementing an iterator is like in Java or Swift. It’s not so bad. But it’s not exactly trivial either. Are there any bugs in this code? It’s not easy to say. It looks nothing like the original for (;;) loop we are trying to emulate here: the iterator protocol forces us to dismantle the loop.

At this point you might be feeling a little lukewarm toward iterators. They may be great to use, but they seem hard to implement.

It probably wouldn’t occur to you to suggest that we introduce a wild, mindbending new control flow structure to the JS language just to make iterators easier to build. But since we do have generators, can we use them here? Let’s try it:

<pre>
function* range(start, stop) {
for (var i = start; i < stop; i++)
yield i;
}
</pre>

See this code in action.

The above 4-line generator is a drop-in replacement for the previous 23-line implementation of range(), including the entire RangeIterator class. This is possible because generators are iterators. All generators have a built-in implementation of .next() and [Symbol.iterator](). You just write the looping behavior.

Implementing iterators without generators is like being forced to write a long email entirely in the passive voice. When simply saying what you mean is not an option, what you end up saying instead can become quite convoluted. RangeIterator is long and weird because it has to describe the functionality of a loop without using loop syntax. Generators are the answer.

How else can we use the ability of generators to act as iterators?

  • Making any object iterable. Just write a generator-function that traverses this, yielding each value as it goes. Then install that generator-function as the [Symbol.iterator] method of the object.

  • Simplifying array-building functions. Suppose you have a function that returns an array of results each time it’s called, like this one:

    <pre>
    // Divide the one-dimensional array 'icons'
    // into arrays of length 'rowLength'.
    function splitIntoRows(icons, rowLength) {
    var rows = [];
    for (var i = 0; i < icons.length; i += rowLength) {
    rows.push(icons.slice(i, i + rowLength));
    }
    return rows;
    }
    </pre>

    Generators make this kind of code a bit shorter:

    <pre>
    function* splitIntoRows(icons, rowLength) {
    for (var i = 0; i < icons.length; i += rowLength) {
    yield icons.slice(i, i + rowLength);
    }
    }
    </pre>

    The only difference in behavior is that instead of computing all the results at once and returning an array of them, this returns an iterator, and the results are computed one by one, on demand.

  • Results of unusual size. You can’t build an infinite array. But you can return a generator that generates an endless sequence, and each caller can draw from it however many values they need.

  • Refactoring complex loops. Do you have a huge ugly function? Would you like to break it into two simpler parts? Generators are a new knife to add to your refactoring toolkit. When you’re facing a complicated loop, you can factor out the part of the code that produces data, turning it into a separate generator-function. Then change the loop to say for (var data of myNewGenerator(args)).

  • Tools for working with iterables. ES6 does not provide an extensive library for filtering, mapping, and generally hacking on arbitrary iterable data sets. But generators are great for building the tools you need with just a few lines of code.

    For example, suppose you need an equivalent of Array.prototype.filter that works on DOM NodeLists, not just Arrays. Piece of cake:

    <pre>
    function* filter(test, iterable) {
    for (var item of iterable) {
    if (test(item))
    yield item;
    }
    }
    </pre>

So are generators useful? Sure. They are an astonshingly easy way to implement custom iterators, and iterators are the new standard for data and loops throughout ES6.

But that’s not all generators can do. It may not even turn out to be the most important thing they do.

Generators and asynchronous code

Here is some JS code I wrote a while back.

<pre>
};
})
});
});
});
});
</pre>

Maybe you’ve seen something like this in your own code. Asynchronous APIs typically require a callback, which means writing an extra anonymous function every time you do something. So if you have a bit of code that does three things, rather than three lines of code, you’re looking at three indentation levels of code.

Here is some more JS code I’ve written:

<pre>
}).on('close', function () {
done(undefined, undefined);
}).on('error', function (error) {
done(error);
});
</pre>

Asynchronous APIs have error-handling conventions rather than exceptions. Different APIs have different conventions. In most of them, errors are silently dropped by default. In some of them, even ordinary successful completion is dropped by default.

Until now, these problems have simply been the price we pay for asynchronous programming. We have come to accept that asynchronous code just doesn’t look as nice and simple as the corresponding synchronous code.

Generators offer new hope that it doesn’t have to be this way.

Q.async() is an experimental attempt at using generators with promises to produce async code that resembles the corresponding synchronous code. For example:

<pre>
// Synchronous code to make some noise.
function makeNoise() {
shake();
rattle();
roll();
}

// Asynchronous code to make some noise.
// Returns a Promise object that becomes resolved
// when we're done making noise.
function makeNoise_async() {
return Q.async(function* () {
yield shake_async();
yield rattle_async();
yield roll_async();
});
}
</pre>

The main difference is that the asynchronous version must add the yield keyword each place where it calls an asynchronous function.

Adding a wrinkle like an if statement or a try/catch block in the Q.async version is exactly like adding it to the plain synchronous version. Compared to other ways of writing async code, this feels a lot less like learning a whole new language.

If you’ve gotten this far, you might enjoy James Long’s very detailed post on this topic.

So generators are pointing the way to a new asynchronous programming model that seems better suited to human brains. This work is ongoing. Among other things, better syntax might help. A proposal for async functions, building on both promises and generators, and taking inspiration from similar features in C#, is on the table for ES7.

When can I use these crazy things?

On the server, you can use ES6 generators today in io.js (and in Node if you use the --harmony command-line option).

In the browser, only Firefox 27+ and Chrome 39+ support ES6 generators so far. To use generators on the web today, you’ll need to use Babel or Traceur to translate your ES6 code to Web-friendly ES5.

A few shout-outs to deserving parties: Generators were first implemented in JS by Brendan Eich; his design closely followed Python generators which were inspired by Icon. They shipped in Firefox 2.0 back in 2006. The road to standardization was bumpy, and the syntax and behavior changed a bit along the way. ES6 generators were implemented in both Firefox and Chrome by compiler hacker Andy Wingo. This work was sponsored by Bloomberg.

yield;

There is more to say about generators. We didn’t cover the .throw() and .return() methods, the optional argument to .next(), or the yield* expression syntax. But I think this post is long and bewildering enough for now. Like generators themselves, we should pause, and take up the rest another time.

But next week, let’s change gears a little. We’ve tackled two deep topics in a row here. Wouldn’t it be great to talk about an ES6 feature that won’t change your life? Something simple and obviously useful? Something that will make you smile? ES6 has a few of those too.

Coming up: a feature that will plug right in to the kind of code you write every day. Please join us next week for a look at ES6 template strings in depth.

About Jason Orendorff

More articles by Jason Orendorff…


14 comments

  1. Jason

    Thanks for the thorough post on this. I’ve been wondering for some time how these work. Any chance you could give an example of a “big ugly function” that you refactored using generators? I’m still having problems visualizing how they’re any better than loops except in the case where you’d want to pause and wait for user interaction…

    May 7th, 2015 at 13:42

    1. Jason Orendorff

      @Jason: Good question. Hmm. It’s hard to find a truly “big” and “ugly” example that’s also small and clean enough to understand.

      Here’s my best shot. This code is “real-esque”. It comes from a real project of mine, with minor simplifications. I’m not super proud of the code quality in this project, but oh well. https://gist.github.com/jorendorff/ad207a791457a27bf446

      That code is Python. Here’s a translation into ES6: https://gist.github.com/jorendorff/4ec9fe31493e1fb24a7e

      May 8th, 2015 at 21:20

  2. Awal

    These posts are pretty good. I’d love to see them come more often, live every two days or something. 1 week is… too much :D

    Very nice read though.

    May 7th, 2015 at 16:33

    1. Jason Orendorff

      @Awal: Thank you. I wish I could post one of these every other day, too! Unfortunately, there’s this whole other part of my job where I’m expected to be writing code. ;-)

      May 8th, 2015 at 21:32

  3. voracity

    If generators/async/await were the only improvements that Javascript were to universally receive in the next 5 years, I’d still be ecstatic. Asynchronous code threatens to shred Javascript to pieces (literally); generators/async/await restore the order (again, literally).

    May 7th, 2015 at 21:37

  4. Phil Stricker

    A “magical feature” indeed! Could see this cut down variable declaration inside of loops and great way to pull any complex logic from inside an iteration.

    May 9th, 2015 at 17:36

  5. Oleg

    Jason, I’ve improved my example from the second post by installing a generator-function as the [Symbol.iterator] method of the object. Look http://codepen.io/anon/pen/OVMrPd?editors=001

    By the way, you probably should explain readers what expression in brackets [] means. This is a “computed property name”. That’s explained very good here https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Object_initializer#Computed_property_names

    Keep it up!

    May 11th, 2015 at 13:03

  6. Andrea Giammarchi

    nice post .. and btw ;-)

    function* range(start, stop) {
    while (start < stop) yield start++;
    }

    May 22nd, 2015 at 15:19

  7. Julian Bravard

    I’m not sure if I’m missing something, but it would appear the variable nRows is never being used in the splitIntoRows example.

    May 23rd, 2015 at 21:24

    1. Jason Orendorff

      You’re right. I’ll remove the dead code.

      May 26th, 2015 at 11:37

  8. Karthick Siva

    Thanks for the article jason. I get it yield returns an object. Would love to hear more about the yield functionality with respect to the stack?

    May 24th, 2015 at 04:34

    1. Jason Orendorff

      Sure – generators don’t change the shape of the stack. The JS stack is still the same data structure it always was: a FIFO stack of frames, one stack frame per function call.

      What’s new is the ability to remove a stack frame from the stack and resume it later — that is, put it back onto the stack later and continue running. Ordinary functions can’t do that. But that’s exactly what yield and .next() do.

      In more detail:

      1. Calling a generator-function creates a new stack frame, but does *not* put it on the stack right away. Instead, it makes a new generator object, stores the new stack frame in there, and returns that.

      2. Calling .next() on a generator object takes the stack frame from the generator object, puts it onto the stack, and starts it actually running.

      3. `yield` removes the current frame from the stack, much like `return`. But instead of just discarding that stack frame, it’s stored in the generator object, so the next time .next() is called, we’ll resume where we left off.

      So a generator object is basically just a JS object representing a single stack frame.

      You can experiment with this by writing some code using generators and doing `alert(Error().stack)` to see the current stack.

      May 26th, 2015 at 12:52

  9. Karthick Siva

    Thanks for the detailed explanation, jason.

    May 26th, 2015 at 22:13

  10. Eric

    Very well-written article. Probably the best I’ve read in explaining what generators are and how they work. Thanks!

    May 28th, 2015 at 10:27

Comments are closed for this article.