NodeJS – Making Asynchronous Programming Easier With Async And Await

In this article, you will learn how you can simplify your callback or Promise based Node.js application with async functions (async/await).

Whether you’ve looked at async/await and promises before, but haven’t quite mastered them yet, or just need a refresher, this article aims to help you.

Before Node version 7.6, the first and the most straightforward solution was in the form of nested functions as callbacks. This solution led to something called callback hell, and too many applications still feel the burn of it.

Then, we got promises. This pattern made the code a lot easier to read, but it was a far away from the Don’t Repeat Yourself (DRY) principle. There were still too many cases where you had to repeat the same pieces of code to properly manage the application’s flow.

The latest addition, in the form of async/await statements, finally made asynchronous code as easy to read and write as any other piece of code.

Let’s take a look at the examples of each of these solutions and reflect on the evolution of asynchronous programming. To do this, we will examine a simple task that performs the following steps:

  1. Verify the username and password of a user.
  2. Get application roles for the user.
  3. Log application access time for the user.

Approach 1: Callback Hell (“The Pyramid of Doom”)

A callback is nothing special but a function that is executed at some later time. The code for the three simple tasks would look something like this:

Each function gets an argument which is another function that is called with a parameter that is the response of the previous action.

Some disadvantages of this are —

  • The code becomes harder to read as one has to move from left to right to understand.
  • Error handling is complicated and often leads to bad code.

That’s where Promises come in.

Approach 2: Promises

To overcome the above problems Promises introduced. A promise is like a placeholder value that is expected to eventually resolve into the final successful result value or reason for failure.

This method did not remove the use of callbacks, but it made the chaining of functions straightforward and simplified the code, making it much easier to read.

A Promise instance has two main methods:

  • then : This runs a callback you pass to it when the promise has finished
  • catch : This runs a callback you pass to it when something went wrong, which caused the promise to reject instead of resolve.

then : This runs a callback you pass to it when the promise has finishedcatch : This runs a callback you pass to it when something went wrong, which caused the promise to reject instead of resolve.

The flow has become a familiar top-to-bottom rather than left-to-right as in callbacks, which is a plus.

However, Promises still suffer from some problems —

  • We still have to give a callback to every .then.
  • Instead of using a normal try/catch, we have to use .catch for error handling.
  • Looping over multiple promises in a sequence is challenging and non-intuitive.

Approach 3: Async/Await

Promises were introduced to solve the famous callback hell problem, but they introduced complexity on their own, and syntax complexity.

ECMAScript 2017 brought in syntactic sugar on top of Promises in the form of async and await statements, making asynchronous code easier to write and to read afterwards.

JavaScript evolved in a very short time from callbacks to promises (ES2015), and since ES2017 asynchronous JavaScript is even simpler with the async/await syntax.They consist of two main keywords – async and await.

They always return a promise, even if you don’t explicitly write them to do so. Also, the await keyword is only available inside async functions at the moment – it cannot be used in the global scope.

async is used to make a function asynchronous. It unlocks the use of await inside these functions.

Async functions pause at each await

await can be put in front of any async promise-based function to pause your code on that line until the promise fulfills, then return the resulting value.

They allow us to write Promise-based code as if it were synchronous, but without blocking the main thread, as this code sample demonstrates:

As you can see in the example above, our code looks very simple. Compare it to code using plain promises, with chaining and callback functions.

How it works

  • Async Functions are declared by prepending the word async in their declaration async function doAsyncStuff() { …code }
  • Your code can be paused waiting for an Async Function with await
  • await returns whatever the async function returns when it is done.
  • If an Async Function throws an exception, the exception will bubble up to the parent functions just like in normal Javascript, and can be caught with try/catch.

Why should you start using the async function today?

  • The resulting code is much cleaner.
  • Error handling is much simpler and it relies on try/catch just like in any other synchronous code.
  • Debugging is much simpler. Setting a breakpoint inside a .then block will not move to the next .then because it only steps through synchronous code. But, you can step through await calls as if they were synchronous calls.

Conclusion

async/await provide a nice, simplified way to write async code that is simpler to read and maintain.

I hope you enjoyed this article on making asynchronous programming easier with async and await in Node.js. If you have any questions or thoughts, share them in the comments, I’ll be there!