Image may be NSFW.
Clik here to view. Whether you’re working on the browser or on the server side, you might’ve run into what I like to call: Callback Soup.
This happens when you start queuing one callback after the other. This is very common when you’re dealing with asynchronous code (AJAX calls, or regular IO on Node.js for instance).
When this happens, we end-up with code that looks like this:
function mainFunction() { getData(function(err, result) { getMoreData(result, function(err2, moreResults) { finallyGetLastData({first: result, second: moreResults}, function(err3, finalResults) { //Do something with all the results here... }); }); }); }
Error handling left out for simplicity reasons
It’s true that having just 3 nested callbacks might not be a big deal, but that’s just an example, we could be dealing with potentially more complex nesting of callbacks and variable dependency (see how the last function actually requires the first result?).
The main problems with this is readability, if we can’t easily see what’s happening and which functions need which variables, then understanding the code and maintaining it gets more difficult.
Dealing with it
So, how do we deal with this problem? A very common strategy is to use Async.js, this library comes with several methods that allow the developer to write the chain of callbacks in a way that’s easier to read. Given, of course, that the person reading the code actually knows how Async works.
That is the main reason why I don’t want to go into much depth regarding the use of libraries to solve a problem that the language itself can solve, if we use it properly.
Dealing with it, the Javascript way
Organizing our code better
The first strategy might sound dumb, but sometimes we don’t really take the time to do this. The code we’re dealing with might be simple enough that just reorganizing it and splitting some of it into different functions might do the trick.
//Main functions function mainFunction() { getData(getDataCB); } function getData(cb) { //Do some async stuff cb(err, result); } function getMoreData(param, cb) { cb(err, {first: param, second: moreResults}); } function finallyGetLastData(param, cb) { cb(err, finalResults); } //Callbacks function getDataCB(err, result) { getMoreData(result, getMoreDataCB); } function getMoreDataCB(err, result) { finallyGetLastData(result, finalCallback); } function finalCallback(err, finalResults) { //Do something with all the results }
That looks cleaner, doesn’t it? Yes, it’s a silly example, but bare with me. Just by passing the reference to a callback, and by keeping a simple standard for all callback functions (first argument is the error, the others are the results) we can simplify and clean our code.
Partial functions to the rescue
The previous approach works, and it’s a good idea to always try to keep your code as clean as possible, reason why I don’t particularly like using 3rd party libraries that end up forcing the developer to code in a way that might be cryptic (or at least, hard to read) for someone who’s not familiar with said library.
Lets take a look at Partial Functions, a nice little perk that Javascript provides us with, thanks to its functional programming side.
Partial functions are, simply put, copies of other functions with pre-set parameters. Thanks to closures, we can create partial functions using the apply
method. Lets take a look at a simple example:
function add(a, b) { return a + b; } function add5(b) { return add.apply(null,[5,b]); }
That’s a very simple example, but we can now do add5(100)
and we’ll get 105.
Now, lets look at something a bit more complicated.
function getData(cb) { cb(null, 1); } function getMoreData(param, cb) { cb(null, param + 1); //We don't really care about the "result" variable } function finallyGetLastData(opts, cb) { cb(null, 400); //We don't care about "result" or "changedResults", the callback will already have them pre-set } function finallyGetLastDataCB(result, changedResults, err, finalResults) { var diff = changedResults - result; console.log("Result: " + result + " - Final Result: " + finalResults); } function getMoreDataCB(result, err, moreResults) { var changedResults = result * 2; finallyGetLastData({ first : result, second : moreResults }, partial(finallyGetLastDataCB, [result, changedResults])); } function mainFunction() { getData(function(err, result){ getMoreData(result, partial(getMoreDataCB, [result])); }); }
We changed the rules a bit, we now changed our callbacks, we added a few attributes to them, before the error attribute. This with the partial
function, allows us to make copies of these callbacks with pre-set arguments. The partial
function will return the actual callbacks, that will be used by the main functions, and those callbacks will only have 2 arguments: error and results.
The code of the partial
function is quite simple:
function partial(func, args) { return function(){ for(var i in arguments) { args.push(arguments[i]); } return func.apply(this, args); } }
We return a wrapper for our callbacks, that will take care of grabbing the pre-sets from the closure created when called, and calling the callback function with the right list of parameters (the pre-sets plus the ones that were passed by the main function).
This way, we can handle complex callbacks that require callbacks down the chain, to use variables declared several steps before.
If you want to know more about partial functions, functional programming in javascript and the like, I’d recommend checking out the AP module.