ES6 Generators – Is This the End of Callback Hell in JS?

11.06.2015 | 5 min read

Oncoming ECMAScript6 standards bring many exciting possibilities to JavaScript. One of these are generators, known well for Python, Ruby, or C# developers. On the web you can find many articles about this subject. Particularly interesting, especially for node.js programmers, are the opportunities to flatten asynchronous code. Unfortunately, with this potential, there is also a lot of confusion and problems understanding the topic. In this article I will explain the basics of generators, show their practical use, and, as the main goal, explain their capabilities in flattening asynchronous code. The reader is expected to have basic knowledge of asynchronous code in JavaScript and at least some experience with Promises pattern.

To run the examples, you will need an environment that supports generators. At the time of writing this article, the obvious solution seems to be node.js in 0.12 version with the –harmony flag enabled.

Generators – What Are They All About?

Basically, a generator is a function whose execution can be stopped at any time and resumed later. Similar solutions have been available in many languages for a long time, as I mentioned in the introduction. How does it look in JavaScript?

def test_func(x):
  return x**2 gives control to the generator and optionally sends to it some value. The keyword yield does the opposite, giving control to the main code and returning a value to it. Most important in understanding the above code is realizing the order of the program sequence. The picture below should be helpful in this. For the implementation details, check the attached materials.

In Practice…

In the beginning, the idea can seem quite complicated. In what way can it actually be useful? I’ll use the language Python , which has supported generators for a long time as an example. Each of you probably use a standard for loop on a daily basis. It can be found in similar form in many languages, including JavaScript :

Python uses, according to many people, a simpler form:

Achieving similar construction in JavaScript should not be difficult. Therefore, let’s try:

Although simple, our solution unfortunately has some drawbacks that can easily escape our attention. The problem is the memory when you try to iterate through very large numbers.

In my case, only 100 million was enough to get the error: process out of memory . The reason is the numbers array, which has to be prepared before starting our for-of loop. It becomes simply too large, and it is unnecessary, because at the specific iteration moment, we need only one number. What if we used generators to solve this same problem?

As you can see, the code is even simpler. We do not need a local array (which was the source of our problems); we return one value at a time. Moreover, we can use the generator in same way as the usual array: we can iterate over it values using the for-of loop!

Flattening Code that Uses Promise

This is the most popular way to use generators. Many people confuse them with this particular solution, but this is just one of the many possibilities they give us.

We’ll show a simplified example of how to flatten your code using Promise mechanism, using the native form of Promise , node.js as well as most leading web browsers providing support for this standard. Let’s start by defining a simple Promise example: a promise that will be fulfilled after the specified number of milliseconds.

Well, it certainly looks better than using native setTimeout and multiple nested callbacks. Unfortunately, we have still a lot of repetitive code. How much better would it look if we could convince our program to simply wait a specified period of time? (Of course, we do not want to suspend the entire application and make our dear user angry.)

We can quite easily teach our program to use the Promise in this way to make the above code work. We need a helper function that, by using generators, handles the entire mechanism for us.

Look closely at the next.value.then(onFulfilled) line. Here we have what might be called an asynchronous recursion. The function does not call itself directly, but indicates that it has to be re-called after fulfilling the Promise . This is a simplified model; however, you do not need more to run our mainGenerator() example. To start it, we simply pass the previously prepared generator to our helper function.

And that’s enough. Our asynchronous but quite readable and flat code executes correctly. As you can see, there is no magic behind it – just a few lines of code and the power of generators.

Unfortunately, our example is not perfect. We make many assumptions. It does not check whether you really yield a Promise . We ignore the value returned from the generator and do not send the fulfilled Promise value. And the most important – lack of errors handling. It’s a good start, but we need to go a little farther.

Promise Rejections Handling

To keep it simple, we assume that the values we pass to yield are actually instances of Promise . Let’s start with two simple promises. delayPromiseProvide() is a Promise of a specific value after passing a certain amount of milliseconds. delayPromiseReject() waits a certain number of milliseconds and rejects with a specified error.

Expected output of calling coroutine(mainGenerator) are progressive printing texts:

This gives us more opportunities. First, our coroutine function returns an instance of the Promise object. We’ll also handle errors and return the result of yielded promises to generator variable ( var val = yield … ). Unfortunately, at this moment our coroutine function is not ready for these tasks. So let’s move forward.

If you understood the first case, this one should not be much more difficult. What is new is the line var next = generator.throw(err) . This code will throw an exception “inside” the generator code, which can also be caught and handled there. It then returns the same object as , with the value obtained from the next yield in the generator function order.

I hope this knowledge of the basics of generators and using them with asynchronous code will prove useful for you.


Generators on MDN

Promises on MDN

ECMAScript 6 Features

Flattening Callbacks Using Promises

You may also like these posts

Start a project with 10Clouds

Hire us