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.



Post Footer automatically generated by Add Post Footer Plugin for wordpress.

About Brad Carleton

Brad is a rockin' fun JavaScript coder who enjoys building cool stuff and long walks on the beach. As the Founder/CTO at TechPines, he has worked with large companies and startups to build cutting-edge applications based on HTML5 and Node.js. He is the creator of Bone.io, a realtime HTML5 framework, and is the author of Embracing Disruption: A Cloud Revolution Manifesto.
This entry was posted in javascript, node.js. Bookmark the permalink. Follow any comments here with the RSS feed for this post.
  • Pat

    I think Promises are a nicer way of achieving what you want here. Context is passed from the return value of one function into the arg of the next, so there’s no need to have a higher-scoped object (which aids in multithreaded dev, if that becomes a possibility). I have a little demo jsbin at http://jsbin.com/nahabiju/12/edit?html,js,console that tries to showcase Promises (using the Q library). Promises are coming along in browsers [http://caniuse.com/#search=promise] and are baked-in (or soon to be) to node, from what I understand.