Async/await in Node.js

This post is 4 years old. (Or older!) Code samples may not work, screenshots may be missing and links could be broken. Although some of the content may be relevant please take it with a pinch of salt.

In this article we'll be discussing how to use the async/await keywords when working with asynchronous Node.js code.

Async/await is available as part of Node.js since version 7.10.1 (or since version 7.5 with the --harmony flag.

From callbacks to promises

In a previous article we have discussed why promises are a great addition to the JavaScript language.

In a nutshell asynchronous programming was first achieved by Node.js using the event loop and the error first callbacks. Although this works, there are multiple problems with using callbacks that may give developers some sleepless nights.

Native support in Node.js for Promises have helped the creation of code where asynchronous programming was enabled by a better flow of programme execution. However, callbacks are still used inside then() statements when a promise is resolved or even inside a catch() block when an error is rejected.

Making promises even better

One of the latest additions to the JavaScript language are the async and await keywords that give us, the developer, an even better way of controling the asynchronicity of code execution. Using async/await truly give us synchronous code execution with a really simple syntax.

If native callbacks were causing callback hell, then async/await is really callback heaven.

In order to use async/await we need to also utilise Promises, so it's important that you understand how they work.

Please note that the await keyword can only be used within functions declared with the async keyword.

As it's name suggest - the await functions allows us to await for a promise - in other words if the promise resolves, its result can be stored in a variable. If the promise rejects it can be caught using a catch statement, which means that ideally a function declared as async should utilise a try/catch block as well.

async/await

Let's take a look at an example where we'd be using multiple promises - this is especially a good use-case for async/await. Consider a scenario where we are making multiple requests to an API and would like to construct a message from the responses from both API calls, this is how our could could look like:

const request = require('request');

function makeRequest(url) {
return new Promise((resolve, reject) => {
request(url, (error, response, body) => {
if (error) {
reject(error);
}
resolve(JSON.parse(body));
});
});
}

let message;
makeRequest('https://swapi.co/api/people/1')
.then((character) => {
message = `${character.name} played in`;
return makeRequest(character.films[0]);
})
.then((film) => {
message += ` ${film.title}.`;
return message;
})
.then((message) => console.log(message))
.catch((error) => console.error(error));

Constructing the final message looks a bit complicated. Using async/await will make the code look a lot more readable. The only thing that we'll save from the previous code example is going to be the makeRequest() method:

const request = require('request');

function makeRequest(url) {
return new Promise((resolve, reject) => {
request(url, (error, response, body) => {
if (error) {
reject(error);
}
resolve(JSON.parse(body));
});
});
}

async function message() {
try {
const character = await makeRequest('https://swapi.co/api/people/1');
const film = await makeRequest(character.films[0]);
console.log(`${character.name} played in ${film.title}`);
} catch (error) {
console.log(error);
}
}

message();

Notice how we are using await to wait for both of the promises to be resolved and we can also make use of the first character variable (containing the resolved promise data already) and use it to make the second request with the second await.

The code above is a lot more readable, easy to follow and it's straight forward to add break points to it.