Promises and Async-Await in Node.js
Learn the importance of promises and how to handle them using async-await.
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.
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:
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 and code that’s difficult to read and maintain. The function will have access to two other functions:callback hell Nested callback function where a promise returns another promise and the chain goes on resolve
: This returns a successful response when the promise is resolved.reject
: This returns a failure when the promise is rejected.
Once the promise is created, we use two functions on a
Promise
object:then()
: This is used when the promise is resolved.catch()
: This is used when the promise is rejected.
Code example
Now let’s have a look at the code.
// Define a simple function to add two numbersfunction sum(num1, num2) {// Returns a promise instead of the response immediatelyreturn new Promise((resolve, reject) => {// Calculating the sumlet sum = num1 + num2;// Resolve the promise if the sum exceeds 10if (sum > 10)resolve("Success: " + sum);// Reject the promise if the sum is less than 10elsereject("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 resolvedconsole.log(res);}).catch((error) => {// The control comes here if the promise is rejectedconsole.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
andreject
.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 statementresolve("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 statementreject("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!
// Define a simple function to add two numbersfunction sum(num1, num2) {// Returns a promise instead of the response immediatelyreturn new Promise((resolve, reject) => {// Calculating the sumlet sum = num1 + num2;// Resolve the promise if the sum exceeds 10if (sum > 10)resolve("Success: " + sum);// Reject the promise if the sum is less than 10elsereject("Failed: " + sum);});}// Creating a function that will call sum() function multiple timesfunction 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() functioncallSum();
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 thesum()
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 thesum()
function is not ready yet. Also, in the console, we can observe anUnhandledPromiseRejectionWarning
due to the second call to thesum()
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.
// Define a simple function to add two numbersfunction sum(num1, num2) {// Returns a promise instead of the response immediatelyreturn new Promise((resolve, reject) => {// Calculating the sumlet sum = num1 + num2;// Resolve the promise if the sum exceeds 10if (sum > 10)resolve("Success: " + sum);// Reject the promise if the sum is less than 10elsereject("Failed: " + sum);});}// Creating an async function that will call sum() function multiple timesasync function callSum() {console.log("Start");try {const ansOne = await sum(9, 20); // Stop the execution until the promise is fulfilledconsole.log(ansOne);const ansTwo = await sum(9, 1); // Stop the execution until the promise is fulfilledconsole.log(ansTwo);} catch (e) {console.log("Exception occured: ", e);}console.log("End");}// Call the callSum() functioncallSum();
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 theasync
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), thecatch
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.
// Importing the axios moduleconst axios = require("axios");// Async function to make the API call and print the responseasync 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 functionfetchData();
Code explanation:
Lines 5–8: We created an
async
function so that we canawait
while making the API call. Then, we called theaxios.get()
method and passed the API, which will fetch a list of 10 users. Note that we added anawait
since we want the response to proceed further. Finally, we printed the response on the console. We also addedtry-catch
block in case the API call fails for some reason, our code will handle it seamlessly.