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.
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}
.
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 Susiripala is a NodeJS Consultant who is well experienced in Server Side Technologies who is recently fell in love with Meteor.