Promises and Async-Await in Node.js

What is a promise?

We often make a web request, an API request, or maybe a database request while creating software applications. These operations usually take a millisecond or probably more to get a connection and get the data back. So if we call it, store the response in a local variable, and try to access the response, we’ll get into trouble (since the data may not be available instantly).

Then, promises come into the picture. These operations usually return a promise, i.e., they say that they “promise” to bring something from the web, database, or any other source in just a few milliseconds or seconds, or else say that it failed.

Press + to interact

A promise in JavaScript is just like a promise in real life. We commit to something by saying we promise to do something. For example, we promise to make the best software we can, which has one of two results: either that promise gets completed (it’s resolved) or fails (it’s rejected). So if we can deliver the best software, we would resolve our promise; but if we failed to provide the best software, then the promise would be rejected.

How to create a promise

Now, let’s look at the actual syntax of creating a promise. We’ll follow the steps mentioned below to create a promise:

  1. We start by using the Promise() constructor and passing a function as a parameter (called a callback function). A callback function alone is insufficient to handle asynchronous operations in JavaScript because they can lead to callback hellNested callback function where a promise returns another promise and the chain goes on and code that’s difficult to read and maintain. The function will have access to two other functions:

    1. resolve: This returns a successful response when the promise is resolved.

    2. reject: This returns a failure when the promise is rejected.

  2. Once the promise is created, we use two functions on a Promise object:

    1. then(): This is used when the promise is resolved.

    2. catch(): This is used when the promise is rejected.

Press + to interact
How promises work
How promises work

Code example

Now let’s have a look at the code.

Press + to interact
// Define a simple function to add two numbers
function sum(num1, num2) {
// Returns a promise instead of the response immediately
return new Promise((resolve, reject) => {
// Calculating the sum
let sum = num1 + num2;
// Resolve the promise if the sum exceeds 10
if (sum > 10)
resolve("Success: " + sum);
// Reject the promise if the sum is less than 10
else
reject("Failed: " + sum);
});
}
// Call the sum() function and use the then() function on the promise returned.
sum(9, 1).then((res) => {
// Printing the success message if the promise is resolved
console.log(res);
}).catch((error) => {
// The control comes here if the promise is rejected
console.log(error);
});

Code explanation:

  • Lines 2–16: We create a function to calculate the sum of two numbers. This function returns a Promise object.

  • Lines 4–15: We create a Promise object that expects a callback function to register the promise. This callback function accepts two parameters: resolve and reject.

  • Lines 9–15: We check if the sum exceeds the value of 10, then we resolve the promise. Otherwise, we reject the promise. We can assume that we might do an API call instead of doing a sum. In this case, if it sends a 200 OK response, we resolve the promise; otherwise, we reject it.

  • Lines 19–25: We first call to the sum() function. This function returns a promise.

    • Line 19: We call the then() method on the promise object. This method is executed if the promise is resolved, and it prints the message returned by the statement resolve("Success") in line 10.

    • Line 22: We call the catch() method on the promise object. This method is executed if the promise is rejected, and it prints the message returned by the statement reject("Failed") in line 13.

  • In the output, we can observe that this promise is rejected since the sum of the two numbers is less than 10.

We can try changing the function's parameters to resolve the promise.

Try promises without then() and catch()

Let’s try something new. We won’t use the then() and catch() methods on the promises returned; instead, we’ll call the sum() method and then print the response to the console. Let’s see what happens!

Press + to interact
// Define a simple function to add two numbers
function sum(num1, num2) {
// Returns a promise instead of the response immediately
return new Promise((resolve, reject) => {
// Calculating the sum
let sum = num1 + num2;
// Resolve the promise if the sum exceeds 10
if (sum > 10)
resolve("Success: " + sum);
// Reject the promise if the sum is less than 10
else
reject("Failed: " + sum);
});
}
// Creating a function that will call sum() function multiple times
function callSum() {
console.log("Start");
const ansOne = sum(9, 20);
console.log(ansOne);
const ansTwo = sum(9, 1);
console.log(ansTwo);
console.log("End");
}
// Call the callSum() function
callSum();

Code explanation:

The code is almost the same. The only difference is from lines 19–29.

  • Lines 19–29: We create a function named callSum() that calls the sum() function multiple times with different parameters and print the response to the console.

  • In the output, we can observe that the sum of the two numbers is not returned; instead, we get a <pending> promise. A <pending> promise means an asynchronous operation was initiated but not yet completed. This means that the result of the sum() function is not ready yet. Also, in the console, we can observe an UnhandledPromiseRejectionWarning due to the second call to the sum() function in line 25, where the sum doesn’t exceed 10.

There’s another way to handle promises to solve this issue and get the response correctly using async-await.

What is async-await?

The async and await keywords are used in conjunction with each other. That means, without an async function, we can’t use the await keyword, or without an await keyword, it doesn’t make sense to make any function async. The await keyword tells JavaScript or Node.js to be specific, to hold the execution of the code until the promise is fulfilled, either resolved or rejected. This will stop the execution at that point and wait for the promise to be fulfilled. Also, to tell JavaScript that there’s an await expression to be used inside a function, the function itself should be defined as async.

Compared with promises, async-await allows for more synchronous-looking code that’s easier to read and maintain. It also simplifies error handling by using try-catch blocks instead of multiple then() and catch() statements for multiple nested promises.

Let’s see how it works in the code.

Press + to interact
// Define a simple function to add two numbers
function sum(num1, num2) {
// Returns a promise instead of the response immediately
return new Promise((resolve, reject) => {
// Calculating the sum
let sum = num1 + num2;
// Resolve the promise if the sum exceeds 10
if (sum > 10)
resolve("Success: " + sum);
// Reject the promise if the sum is less than 10
else
reject("Failed: " + sum);
});
}
// Creating an async function that will call sum() function multiple times
async function callSum() {
console.log("Start");
try {
const ansOne = await sum(9, 20); // Stop the execution until the promise is fulfilled
console.log(ansOne);
const ansTwo = await sum(9, 1); // Stop the execution until the promise is fulfilled
console.log(ansTwo);
} catch (e) {
console.log("Exception occured: ", e);
}
console.log("End");
}
// Call the callSum() function
callSum();

Code explanation:

The code is almost the same. The only difference is from lines 19–32.

  • Line 19: We define callSum() as an asynchronous function using the async keyword.

  • Lines 21–30: We enclose the statements under a try-catch block. This is done because if the promise is fulfilled and rejected (in line 25), the catch block handles that exception and throws the right message.

  • Lines 22–25: We use the await keyword to stop the execution at that point until the promise is resolved and the data is ready.

In this way, we can handle promises using async-await instead of the then() and catch() methods. Assume that a promise is returning another promise, that promise is again returning another promise, and so on. By using the then() and catch() methods, the code may become too complicated to understand, and using async-await can help to write better code that looks synchronous.

Real-life example of async-await using an API call

The example that we discussed on async-await does not reflect a real-life use case. Let's go and understand how this would work while making an API call. To make an API call, we are going to use axios package that will help to make any sort of API calls be it GET, POST, PUT, etc. For now, let's stick to a dummy GET API call which will return a list of 10 users.

Press + to interact
// Importing the axios module
const axios = require("axios");
// Async function to make the API call and print the response
async function fetchData() {
try {
const request = await axios.get("https://jsonplaceholder.typicode.com/users");
console.log(request.data);
} catch (e) {
console.log(e);
}
}
// Call the async function
fetchData();

Code explanation:

  • Lines 5–8: We created an async function so that we can await while making the API call. Then, we called the axios.get() method and passed the API, which will fetch a list of 10 users. Note that we added an await since we want the response to proceed further. Finally, we printed the response on the console. We also added try-catch block in case the API call fails for some reason, our code will handle it seamlessly.