Taming Callback Hell in Node.js
One of the first things that you’ll hear about Node.js is that it’s async and it uses callbacks everywhere. In some ways this makes Node.js more complex than your typical runtime, and in some ways it makes it simpler.
When executing long sequences of async steps in series, it can be easy to generate code that looks like this:
// callback hell exports.processJob = function(options, next) { db.getUser(options.userId, function(err, user) { if (error) return next(err); db.updateAccount(user.accountId, options.total, function(err) { if (err) return next(err); http.post(options.url, function(err) { if (err) return next(err); next(); }); }); }); };
This is known as callback hell. And while there is nothing wrong with the code itself from a functional perspective, the readability of the code has gone to hell.
One problem here, is that as you add more steps, you will see your code fly off the right of the screen (as each callback will add an additional nesting/indentation level). This can be unnerving for us developers since we tend to read code better vertically than horizontally.
Luckily Node.js code is simply javascript, which means that you actually have a lot of flexibility in how you structure your code. You can make the snippet above much more readable by shuffling some functions around.
Personally, I’m partial to a little Node.js library called async, so I’ll show you how I use one of their constructs async.series to make the above snippet more readable.
exports.processJob = function(options, next) { var context = {}; async.series({ getUser: function(next) { db.getUser(options.userId, function(error) { if (error) return next(error); context.user = user; next(); }); }, updateAcount: function(next) { var accountId = context.user.accountId; db.updateAccount(accountId, options.amount, next); }, postToServer: function(next) { http.post(options.url, next); } }, next); };
What did we just do?
The async.series function can take either an array or an object. The nice thing about handing it an object is that it gives you the opportunity to do some lightweight documentation of the steps by giving the keys of the objects names, like getUser, updateAccount, and postToServer.
Also, notice how we solved another problem, which is sharing state between the various steps by using a variable called “context” that we defined in an outer scope.
One cool thing about Node.js that async shows off quite nicely is that we can easily change these steps to occur in parallel using the same coding paradigm. Just change from async.series to async.parallel.
The asynchronous archtiecture of Node.js can help make applications more scalable and performant, but sometimes you have to do a little extra to keep things manageable from a coding perspective.
</link>