Node.js - async your way out of callback hell with Promises, Async & Async/Await

In this blog, I will try and compare the various methods to avoid the dreaded callback hells that are common in Node.js. What exactly am I talking about? Have a look at this piece of code below. Every child function executes only when the result of its parent function is available. Callbacks are the very essence of the unblocking (and hence performant) nature of Node.js.

Convinced yet? Even though there is some seemingly unnecessary error handling done here, I assume you get the drift! The problem with such code is more than just indentation. Instead, our programs entire flow is based on side effects - one function only incidentally calling the inner function.

There are multiple ways in which we can avoid writing such deeply nested code. Let's have a look at our options:


Promises

According to the official specification, promise represents an eventual result of an asynchronous operation. Basically, it represents an operation that has not completed yet but is expected to in the future. The then method is a major component of a promise. It is used to get the return value (fulfilled or rejected) of a promise. Only one of these two values will ever be set. Let's have a look at a simple file read example without using promises:

Now, if readFile function returned a promise, the same logic could be written like so:

The fileReadPromise can then be passed around multiple times in a code where you need to read a file. This helps in writing robust unit tests for your code since you now only have to write a single test for a promise. And more readable code!


Chaining using promises

The then function itself returns a promise which can again be used to do the next operation. Changing the first code snippet to using promises results in this:

As in evident, it makes the code more composed, readable and easier to maintain. Also, instead of chaining we could have used Promise.all (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/all). Promise.all takes an array of promises as input and returns a single promise that resolves when all the promises supplied in the array are resolved. Other useful information on promises can be found here: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise


The async utility module

Async is an utility module which provides a set of over 70 functions that can be used to elegantly solve the problem of callback hells. All these functions follow the Node.js convention of error-first callbacks which means that the first callback argument is assumed to be an error (null in case of success). Let's try to solve the same foo-bar-baz problem using async module. Here is the code snippet:

Here, I have used the async.waterfall function as an example. There are a multiple functions available according to the nature of the problem you are trying to solve like async.each - for parallel execution, async.eachSeries - for serial execution etc. More details on the official documentation - http://caolan.github.io/async/docs.html


Async/Await

Now, this is one of the most exciting features coming to Javascript in near future. It internally uses promises but handles them in a more intuitive manner. Even though it seems like promises and/or 3rd party modules like async would solve most of the problems, a further simplification is always welcome! For those of you who have worked with C# async/await, this concept is directly cribbed from there and being brought into ES7. 

Async/await enables us to write asynchronous promise-based code as if it were synchronous, but without blocking the main thread. An async function always returns a promise whether await is used or not. But whenever an await is observed, the function is paused until the promise either resolves or rejects. Following code snippet should make it clearer:

Here,  asyncFun is an async function which captures the promised result using await. This has made the code readable and a major convenience for developers who are more comfortable with linearly executed languages, without blocking the main thread. 

Now, like before, lets solve the foo-bar-baz problem using async/await. Note that foo, bar and baz individually return promises just like before. But instead of chaining, we have written the code linearly.


How long should you (a)wait for async to come to fore?

Well, its already here in the Chrome 55 release and the latest update of the V8 engine.  The native support in the language means that we should see a much more widespread use of this feature (https://www.chromestatus.com/feature/5643236399906816). The only, catch is that if you would want to use async/await on a codebase which isn't promise aware and based completely on callbacks, it probably will require a lot of wrapping the existing functions to make them usable.

To wrap up, async/await definitely make coding numerous async operations an easier job. Although promises and callbacks would do the job for most, async/await looks like the way to make some architectural problems go away and improve code quality. 


Harshad is a passionate software engineer and has experience in writing scalable applications using Django and Python. He has recently been dabbling in the world of NodeJS and is loving it.

When he is not coding, you can find him lurking on Reddit"