Callback Hell, also known as Pyramid of Doom, is an anti-pattern seen in code of asynchronous programming.
It is a slang term used to describe and unwieldy number of nested "if" statements or functions.
If you are not expecting your application logic to get too complex, a few callbacks seem harmless. But once your project requirements start to swell, you will quickly find yourself piling layers of nested callbacks. Congrats! Welcome to Callback Hell.
A Callback is a function “A” that is passed to another function “B” as a parameter. The function “B” executes the code “A” at some point. The invocation of “A” can be immediate, as in a synchronous callback, or, it can occur later as in an asynchronous callback. It’s actually a simple concept that will be well understood with an example:
In the code we can see how to make a call to readFile and we pass it as a second parameter function (Callback Hell). In this case, readFile is an asynchronous operation and when it’s done with the operation of reading the file, it will execute the callback by passing the results of the operation to parameters.
The use of callbacks makes the code difficult to write and maintain. It also increases the difficulty of identifying the flow of the application, which is an obstacle when it comes to making debug, hence the famous name to this problem: Callback Hell.
Q: What's worse than callback hell?
A: Not fixing it.
So it is definitely recommended to do it right from the get-go and avoid deeply-nested callbacks. My favourite solution for this will be the usage of the Promise object. I have been dealing with Node.js for my last few projects and Promise managed to keep my sanity in check. But if you are looking for something more edgy, you will love Generators. Another elegant approach to get rid call back hell, is to use async.waterfall. I will touch more in depth about all the approaches below.
A promise is the future result of an asynchronous operation and is represented as a JavaScript object with a public interface. If we apply the code above, we would be left with the following code:
While it is true that by using promises the code is more readable, we still end up having a problem; we have an application that is difficult to debug and maintain. Again, it is very easy to lose track of what is happening in the application, since we are talking about “future” results. Therefore, we will first have to convert all these APIs based on callbacks to Promises. That is when the coroutines (Fibers) are quite helpful.
Generators, like Promise, is also one of the features in ES6 that can manage asynchronous programming. The great thing about Generators is that it can work hand-in-hand with Promise to bring us closer to synchronous programming.
Pro-tip: If you cannot see how this might look synchronous, strip away the function *() and yield.
From the above example, we are coding in a synchronous manner with Generators- using the try and catch block and writing the code as if the result is returned immediately. There are also no verbosity with the then handler.
Javascript function are expected to run-to-completion - This means once the function starts running, it will run to the end of the function. However, Generators allow us to interrupt this execution and switch to other tasks before returning back to the last interrupted task.
Async Waterfall is amazing simple and powerful technique to get out of callback hell. On top of that it makes code readable, easy to understand and even easy to maintain and debug. Let’s take an example of reading a json file.
First divide the code into simple asynchronous step functions that need to be executed to perform a given task. In the example of reading json file, the steps could be reading the json file and processing the file once read. Remember each step function takes a callback as argument. The first parameter to callback is error object. If error object is not null the waterfall stops further processing and error handler is called with error object. which takes parameters to next step function as argument along with arguments required.
Read the file:
Process the file:
Note that I did no specific error handling here, I'll take benefit of async.waterfall to centralize error handling at the same place.
Also be careful that if you have (if/else/switch/...) branches in an asynchronous function, it always call the callback one (and only one) time.
Plug everything with async.waterfall
Note how easy to understand and maintain the code is.
Node.js is a free, open source runtime environment for executing JavaScript code that runs on various platforms. It is used for highly scalable, data-intensive and real time apps due to its non-blocking/asynchronous nature.
Like any other platform, Node.js is also vulnerable to developer problems and issues. Some of these mistakes degrade performance, while others make Node.js appear straight out unusable for whatever you are trying to achieve.
module.export.verifyPassword = function(user, password, callback){
if(typeof password !== ‘string’) {
done(new Error(‘password should be a string’))
return
}computeHash(password, user.passwordHashOpts, function(err, hash) {
if(err) {
done(err)
return
}
done(null, hash === user.passwordHash)
})
}
Techniques to fix callback hell