Node Callback Hell

I may be a little late, but I’m hopping aboard the node train! Having a more than a decade under my belt of php and roots in C/C++ helps tremendously in seeing the bigger picture with node. IMO, it’s a great platform! Having said that, there are quite a few caveats that you must address. I’ll be covering a few this month and the first is: Callback Hell!

Callbacks are great, but they can quickly cause your code to be 5 levels deep. Here’s an example:

const verifyAuth = function( token, callback ) {
  var token_query = 'SELECT * FROM users WHERE token = ?';
  var roles_query = 'SELECT * FROM user_roles WHERE user_id = ?';
  mysql.query( token_query, [token], function(err, rows) {
    if ( err ) {
      callback(err);
    } else {
      if ( rows.length == 0 ) {
        callback('User not found');
      } else {
        var user = rows[0];
        mysql.query( roles_query, [user.user_id], function(err, rows) {
          if ( err ) {
            callback(err);
          } else {
            callback( null, user, rows );
          }
        })
      }
    }
  });
};

Here, you see there are 2 separate db queries. An initial solution is to name and then split out the separate functions:

function lookupToken( token, callback ) {
  var token_query = 'SELECT * FROM users WHERE token = ?';
  mysql.query( token_query, [token], callback );
}

function lookupUserRoles( user, callback ) {
  var roles_query = 'SELECT * FROM user_roles WHERE user_id = ?';
  mysql.query( roles_query, [user.user_id], callback );
}

const verifyAuth = function( token, callback ) {
  lookupToken( token, function(err, user) {
    if (err) {
      return callback(err);
    }
    lookupUserRoles( user, callback );
  })
}

That’s a great first step! Now, let’s travel down the rabbit hole, with async. This allows you to load the 2 db calls in sequence:

const async = require('async');

const verifyAuth = function( token, callback ) {
  var token_query = 'SELECT * FROM users WHERE token = ?';
  var roles_query = 'SELECT * FROM user_roles WHERE user_id = ?';
  var user_record;
  var roles_records;

  async.series( function lookupUser(done) {
    mysql.query( token_query, [token], function(err, rows) {
      if ( err ) {
        return done(err);
      }
      if ( rows.length == 0 ) {
        return done('User not found');
      }
      user_record = rows[0];
      done()
    })
  }, function lookupUserRoles(done) {
    mysql.query( roles_query, [user.user_id], function(err, rows) {
      if ( err ) {
        return done(err);
      }
      roles_records = rows;
      done()
  }, function allDone( err ) {
    callback( err, user_record, roles_records );
  })
};

It doesn’t stop there! You can also iterate of an array in parallel. For example, if you want to update many records at once, you can use async.each:

const async = require('async');

const saveRecords = function( items, callback ) {
  var query = 'UPDATE item SET value = ? WHERE id = ?';
  async.each( items, function saveItem(item, done) {
    mysql.query( query, [item.value, item.id], done );
  }, function allDone( err ) {
    callback( err );
  })
};

While you can’t remove callbacks completely, my current rule of thumb is to not have any code with more than 5 indentations, so 2 space = 1 indent means no lines with more than 10 spaces at the beginning.

Re-using custom npm modules

From time to time, there will be a need to include a customized node module across multiple projects. This may be because the original has a bug and you know how to fix it, or simply creating a new re-usable module. Luckily, there’s an easy way to do this using github.┬áIf you’re new to github, I’d suggest reading up on it before proceeding.

Step 1: Push your module to github

Prepare your module, as usual. Instead of sending it to npm, just push it up to a new github repo. If you’re working on fixing or updating a module that already exists, you can just fork the module’s github repo and work in your fork!

Step 2: Installing from github

This is really easy:

npm install --save username/repo

Here’s a real example:

npm install --save barrygilbert/mysql-restapi

You can also reference a specific branch:

npm install --save barrygilbert/mysql-restapi#master

Step 3: Do your thing!

Now that you can install that module into multiple projects, go code it up!