How to avoid the infamous callback hell in Node.js
#Where is the trouble coming from?
Node has the nice feature of being an asynchronous platform. As a consequence, you execute functions and deal with the response in a callback. Indeed Node is running a single event loop and is of course not waiting for your asynchronous call to return before jumping to some other work to do. That’s very handy and cool but comes with a major drawback; if you do not pay attention to your code, you will be doomed with blocks like:
The deeply nested code makes it not only difficult to read but a challenge to maintain as well.
##Introcing the classic node.js callback
Because your calls are async, dealing with the response will be done later, when the response is actually received. Therefore you will pass to node a function -callback- to be executed in the future.
###Error handling
Errors cannot be managed by a traditional try/catch block because your try/catch is executed synchronously and won’t wait
for the response of your async call. It will just consider everything went fine.
Therefore, there is a convention: your callback has to be written like a function with two arguments: function(error, response){}
The first argument will contain an error if any or null and the second one will contain the response in case of success.
Yet synchronous code will still be handled by try/catch, so you will have to alternate between the two, not ideal.
Let’s look at the following example where we read asynchronously the content of a file.
We have to check for err and throw the error. Things get messier when more nested callbacks come into play. Error management tends to be complicated and repeated.
###Calling the callback gets repeated and forgotten…
Let’s imagine a second that we want to do something with the data read in the example above but we do not what when
writing this function. It will be determined by the caller.
We rewrite the snippet:
The issue here is that you have to call the callback in multiple places and it gets worse when adding more control flows like if/else. It is very easy to forget to call the callback somewhere and the consequence is code which will never run. It will lead in most scenario in a request which will hang forever and time out eventually on the client.
###Parallization isn’t beautiful
Our approach here is to keep a counter which holds the total number of initial operations. It will be decreased each time an operation is completed and tested against 0. 0 would mean everything is completed and we can return.
Having to explicitly deal with the counter ourselves is unsatisfactory. Imagine now that you mix that with chains of deeply nested async calls and get the hellish part.
Note that because Node runs on a single loop, we do not have to care about concurrent threads accessing counter at the same time and having to lock it.
#Do not go into the mess
##Use named functions
By using named function instead of anonymous functions in nested callbacks, you will not only improve readibility but also help when unit testing your functions. Let’s look back at Example 1 and improve on it.
##Use async library
For next-level code, i strongly advise you to use the following async library. Your code will read a lot better and your blocks will be shallower.
This library is very powerful and you could as easily just run the two functions in // with async.parallel
The documentation is complete and well done so I simply invite you to read it on Github.
It won’t solve the error management hassle as you still deal with callbacks
##Use promises
While callbacks were once described as a Node feature, I always felt this was more a statement to hide the simple truth that Node
was missing Futures or Promises, whatever you call them. Simply said, a promise is a container holding the result of an
asynchronous computation.
They are better because they allow you to write code almost the same way you would write synchronous code, including errors
with catch!
###Bluebird as a fast promise library
The most used and fastest library for Node.js is probably bluebird
####Transform callbacks into promises with promisification
Bluebird comes with an api to automatically transform a callback function which follow the node convention (err, response) into a promise.
Be careful not to mix callbacks within promises as catch would not work as expected. Don’t do this:
There are a few node.js promises library, bluebird is currently the fastest. Check out this benchmark as a proof.]