How yield will transform Node.js • Alex MacCaw
How yield will transform Node.js
Node v0.11.2 was released recently, and with it support for v8 generators. Generators can be hard to grasp, but essentially they’re decorated functions that you can execute multiple times and resume execution at different points inside the function.
What this means in practice is that we can get rid of the callback hell that has plagued node applications, and write code in a synchronous style, while it’s executed asynchronously behind the scenes.
Using yield
A code example is worth a thousand words, so let’s dive right in. We’re going to need Node v0.11.2, which you can easily install with nvm.
nvm install 0.11.2
We’re going to use a promise based server called Mach, and a promise library called Q. Install both of those with npm if you haven’t already.
Since we’re using bleeding edge v8 features, you’ll need to pass the --harmony
flag through to Node whenever you invoke it.
node --harmony ./generators.js
Let’s define a sleep()
function that takes an amount in milliseconds to sleep, and returns a promise. The promise will be resolved when the timeout completes.
function sleep(millis) {
var deferredResult = Q.defer();
setTimeout(function() {
deferredResult.resolve();
}, millis);
return deferredResult.promise;
};
A traditional callback based server would use promise chaining, and look like the following.
var mach = require('../lib');
var app = mach.stack();
var Q = require('q');
app.run(function(request) {
return sleep(2000).then(function(){
return 'Good day to you sir';
});
});
mach.serve(app, 3333);
Now let’s see what this looks like using generators. We’re going to define a generator function using the *
notation. Then inside the function we can use yield
, passing the sleep()
promise.
app.run(Q.async(function *(request) {
yield sleep(2000);
return 'Good day to you sir';
}));
As soon as yield
is invoked, execution will pause and jump back up to Q.async()
. Q will ensure that execution is resumed as soon as the promise is complete, which in our case will happen as soon as the timeout finishes. Q.async()
itself returns a promise to Mach, which serves the request.
Real world yield
Now sleeping might be a fairly useless example, but let’s take a look at a better one – accessing a request’s parsed content. Mach let’s you do this with the request.parseContent()
function. Since it returns a promise we can pipe that straight into yield
:
app.run(Q.async(function *(request) {
var body = yield request.parseContent();
return JSON.stringify(body);
}));
You can start to see how yield
is cleaning up your Node handlers. Let’s take a look at another example – saving a model.
app.post('/users', Q.async(function *(request) {
var user = new User(request.params);
if (yield user.save()) {
return JSON.stringify(user);
} else {
return 422;
}
}));
Next steps for yield
I honestly think yield
is going to really transform the way Node applications are written, and cleanup up some of the traditional callback spaghetti you find in asynchronous code. I also can’t wait until it’s available in more browsers, although I suspect that will take a while.
You can find a full example of using Mach and generators in my fork.