JavaScript Generators and Preventing Callback Hell

Share this article

Key Takeaways

  • JavaScript generators, a new feature in JavaScript, are a type of function that act as an iterator, emitting new items using the ‘yield’ keyword. They can be used to overcome issues with callback hell and asynchronous error handling in large Node applications.
  • Thunks, partially evaluated functions that accept a single callback as the argument, can be used within generators to write programs without callbacks. The ‘co’ module further simplifies this process by allowing thunks and generators to be used together to create Node.js applications without callbacks.
  • Despite being fairly new and generally unavailable, generators are gaining interest in the Node community, as demonstrated by the release of Koa, a generators-friendly clone of Express. With increased support, generators could simplify the process of writing large, modular Node apps.
Node.js is the one of the best technologies for building I/O intensive web applications. JavaScript’s single threaded nature provides many advantages over the thread based execution model found in other programming languages for this class of applications. But, these advantages come at a price. Writing large Node applications can be difficult due to callback hell and asynchronous error handling. There are a number of solutions for overcoming these issues. Fibers and Promises are two of them. Some developers prefer using these solutions, but it all comes down to personal preference. The yet to be released ECMAScript version 6 also introduces generators as a solution to callback hell. This articles gives you a proper introduction to generators and shows how they can be used to solve the issues mentioned above.

Prerequisites

As I mentioned, generators are a new feature in JavaScript. The current stable release of Node (0.10.x) does not include generators. So, we’ll need to install the current unstable 0.11.x release in order to use generators. Once you’ve installed 0.11.x, you can enable generators by passing the --harmony-generators flag to Node, as shown below. node --harmony-generators <filename.js>

Generators 101

Simply put, generators are a type of function (note the * in the following code sample) which act as an iterator. Generators can contain any valid JavaScript code. Let’s write our first generator (shown below).
function* HelloGen() {
  yield 100;
  yield 400;
}

var gen = HelloGen();

console.log(gen.next()); // {value: 100, done: false}
console.log(gen.next()); // {value: 400, done: false}
console.log(gen.next()); // {value: undefined, done: true}
yield is a special keyword which emits a new item from the generator. We can use next() to get values from a generator. Once we reach the end of the iterator, the returned object will contain done: true. Any data type can be yield
ed, including functions, numbers, arrays, and objects. Values can also be passed to generators as shown below.
function* HelloGen2() {
  var a = yield 100;
  var b = yield a + 100;

  console.log(b);
}

var gen2 = HelloGen2();

console.log(gen2.next());     // {value: 100, done: false}
console.log(gen2.next(500));  // {value: 600, done: false}
console.log(gen2.next(1000)); // {value: undefined, done: true}
// prints 1000

Preventing Callback Hell

So, how can generators be used to avoid callback hell? First, you need to understand a simple technique that we will be using heavily with generators to write code without callbacks.

Understanding Thunks

A thunk is a partially evaluated function which accepts a single callback as the argument. Within generators, we’ll be yielding thunks to write programs without callbacks. A simple thunk is shown below.
function(callback) {
  fs.readFile('myfile.md', 'utf8', callback);
}
Thunks can also be created dynamically as shown below.
function readFile(filename) {
  return function(callback) {
    fs.readFile(filename, 'utf8', callback);
  };
}

Using co

co is a nice module which helps to use thunks and generators together to create Node.js applications without callbacks. I’ll show you how it works internally later. For now let’s try co, which can be installed using the command npm install co. A simple application which uses co and the readFile() thunk from the previous example is shown below.
var co = require('co');

co(function* () {
  var file1 = yield readFile('file1.md');
  var file2 = yield readFile('file2.md');

  console.log(file1);
  console.log(file2);
})();
As you can see, we are no longer using callbacks. This gives us a simple way to write large modular Node apps easily.

How co Works Internally

You might be wondering how co works internally. Here’s how it works its magic.
  • First, it calls next(null) and gets a thunk.
  • Then, it evaluate the thunk and saves the result.
  • Then, it calls next(savedResult).
  • Repeat these steps until next() returns {done: true}.
If you prefer sample code, here’s a minimal version of co written to show you how it works internally. co is more complex than this, since it does better error handling and supports promises as well.
function co(generator) {
  var gen = generator();

  function nextItem(err, result) {
    var item = gen.next(result);

    if (!item.done) {
      item.value(nextItem);
    }
  }

  nextItem();
}

Modules That Can Be Used with co

co can be used with any module that utilizes thunks. Unfortunately, there are not many modules that currently make use of thunks. You can see the complete supported list here. With simple utilities like thu and thunkify, you can wrap any Node module as thunks for use with co.

Conclusion

Generators are fairly new, and generally unavailable. However, the Node community seems to be showing a lot of interest. One of the best example is the release of Koa. It is a generators friendly clone of Express built by the same team who built Express. I’m sure as time goes on, there will be increased support for generators from the community.

Frequently Asked Questions (FAQs) about JavaScript Generators and Preventing Callback Hell

What is a JavaScript generator and how does it work?

A JavaScript generator is a special type of function that works as a factory for iterators. The function generates a sequence of values, not by returning an array that it builds and fills, but by yielding them one at a time. When a generator function is called, it returns a special iterator, called a generator. This generator can be controlled by the methods next(), return(), and throw(). Each time the next() method is called, the generator function resumes from where it last left off, running until it reaches the next yield statement.

What is callback hell and how can it be prevented?

Callback hell, also known as Pyramid of Doom, refers to heavily nested callbacks that make code hard to read and debug. It’s a common issue in JavaScript and Node.js, where asynchronous programming is the norm. Callback hell can be prevented by using techniques like modularization, which involves breaking callbacks into independent functions, or using Promises and async/await syntax. JavaScript generators can also be used to prevent callback hell by making asynchronous code look and behave like synchronous code.

How do JavaScript generators help in preventing callback hell?

JavaScript generators can help prevent callback hell by making asynchronous code look and behave like synchronous code. Generators allow us to pause and resume code execution, which can be very useful when dealing with asynchronous operations. Instead of nesting callbacks, we can yield an asynchronous operation, pause the execution of our code until the operation is complete, and then resume with the result of the operation.

Can JavaScript generators be used with Promises?

Yes, JavaScript generators can be used with Promises to manage asynchronous operations. When a Promise is yielded in a generator, the generator pauses until the Promise is resolved. Once the Promise is resolved, the generator resumes, and the resolved value of the Promise is returned by the yield expression. This makes it easier to write and understand asynchronous code.

What are the differences between JavaScript generators and regular functions?

Unlike regular functions, generator functions in JavaScript can be paused and resumed, allowing them to be used for asynchronous programming. Regular functions run to completion without any pause, while generator functions can yield multiple values at different times. When a generator function is called, it returns a generator object with methods like next(), return(), and throw(), instead of executing all of its statements.

How can I create a JavaScript generator?

A JavaScript generator is created by defining a function with the function* syntax. Inside the generator function, you can use the yield keyword to pause the function and return a value. The function can be resumed later from where it left off, allowing it to yield multiple values over time.

Can JavaScript generators be used for infinite sequences?

Yes, JavaScript generators can be used to produce infinite sequences. Because generators only compute their yielded values on demand, they can represent sequences of any length, including infinite ones. This can be useful for sequences that are too large to fit in memory, or for sequences that are computationally expensive to generate.

What is the role of the yield keyword in JavaScript generators?

The yield keyword is used in JavaScript generators to pause the function and return a value. When the generator’s next() method is called, the function resumes from where it left off, until it reaches the next yield statement. The value of the yield expression is the argument passed to the next() method.

Can I use JavaScript generators in Node.js?

Yes, JavaScript generators are part of the ECMAScript 2015 (ES6) specification and are supported in Node.js. They can be used to write asynchronous code that looks and behaves like synchronous code, making it easier to read and debug.

Are there any limitations or drawbacks to using JavaScript generators?

While JavaScript generators can make asynchronous code easier to write and understand, they also have some limitations. For example, they can’t be used with arrow functions, and they’re not as efficient as regular functions for simple use cases. Also, because they’re part of the ES6 specification, they’re not supported in older browsers without a transpiler like Babel.

Arunoda SusiripalaArunoda Susiripala
View Author

Arunoda Susiripala is a NodeJS Consultant who is well experienced in Server Side Technologies who is recently fell in love with Meteor.

ECMAScript6
Share this article
Read Next
Get the freshest news and resources for developers, designers and digital creators in your inbox each week