What can I use to replace nested async callbacks?

Lets say I wanna send an email then update the database, both actions are async. This is how I would normally write it.

send_email(function(err, id){
    if(err){
        console.log("error");
    }else{
        update_database(id,function(err, id){
            if(err){
                console.log("error");
            }else{
                console.log("success");
            }
        });
    }
});

I would like to do this instead with middleware.

var mid = {};

mid.send_email = function(){
    return function(next){
        send_email(function(err,id){
            if(err){
                console.log("error");
            }else{
                next(id);
            }
        });
    }
}

mid.update_database = function(){
    return function(id,next){
        update_database(id,function(err,id){
            if(err){
                console.log("error");
            }else{
                next(id);
            }
        });
    }
}

mid.success = function(){
    return function(id,next){
        console.log("success")
        next(id);
    }   
}

Stacking the middleware.

middleware.use(mid.send_email());
middleware.use(mid.update_database());
middleware.use(mid.success());

There are two main questions at hand.

  • How can I use middleware in place of nested callbacks?
  • Is it possible to pass variables to next()?

What you want is to be able to handle a async control flow. Alot of js library can help you to achieve this. You can try the Async library with the waterfall function since you want to be able to pass variables to the next function that will be executed :

https://github.com/caolan/async#waterfall

"Runs an array of functions in series, each passing their results to the next in the array. However, if any of the functions pass an error to the callback, the next function is not executed and the main callback is immediately called with the error."

Example :

async.waterfall([
    function(callback){
        callback(null, 'one', 'two');
    },
    function(arg1, arg2, callback){
        callback(null, 'three');
    },
    function(arg1, callback){
        // arg1 now equals 'three'
        callback(null, 'done');
    }
], function (err, result) {
   // result now equals 'done'    
});

You are probably better off using CommonJS module.exports.

You can create a file like this:

module.exports = function (){
    function sendEmail(doneCallback){
        // do your stuff, then when you are done:
        if(!err){
            doneCallback(whatever,args,you,need);
        }
    }

    function updateDB(success){
        // do your stuff, then when you are done:
        success(whatever,args,you,need);
    }

    return {
        send: sendEmail,
        update: updateDB
    };
};

Then in your server.js:

var lib = require('./mylib.js');

lib.send(function(result){
   console.log(result);
});

This is a similar pattern, and it might give you a better idea of what I mean. It consists of the library baking a function and passing it to whoever needs to chain, like this (more down to earth example, client-side this time):

ui.bistate($('#mybutton'), function(restore){
    $.ajax({
        url: '/api/1.0/catfood',
        type: 'PUT',
        data: {
            catfood: {
                price: 1.23,
                name: 'cheap',
                text: 'Catzy'
            }
        }
    }).done(function(res){
        // stuff with res
        restore();
    });
});

and in the library, this is how restore is provided:

var ui = function(){
    function bistate(button, action) {
        var originalText = buttonText.data('text'),
            disabledText = buttonText.data('text-disabled');

        function restore(){
            button.prop('disabled', false);
            button.text(originalText);
        }

        function disable(){
            button.prop('disabled', true);
            button.text(disabledText);
        }

        button.on('click', function(){
            disable();
            action(restore);
        });
        restore();
    }

    return {
        bistate: bistate
    };
}();

Allowing the consumer to control the flow for when he wants to restore the button, and reliving the library from having to handle complex cases where the consumer wants to do an async operation in between.

In general the point is: passing callbacks back and forth is huge and not used widely enough.

I have been using Queue.js in my work for some time.