Async Await in Node.js: How to Guide With Examples

Read it in 10 Mins

Published
19th Oct, 2022
Views
3,539
Async Await in Node.js: How to Guide With Examples

The need to master asynchronous calls and design patterns in Node.js is too crucial to be ignored. The entire language is based upon an asynchronous model. Once you master asynchronous JavaScript with Node.js, you will never again go back to the basics. Become a pro at node.js with the help of the Node.js course online.

Let’s start this asynchronous journey by understanding its fundamentals… 

Fundamentals of Asynchronous Programming

A function takes time to retrieve data from an API. Asynchronous programming was created to account for the time lag between when a function is called and when its value is returned. Asynchronous programming is a programming model that allows a user to work in an application while processes run in the background, improving the user experience.

Things happen one at a time in the synchronous programming model. When you call a function that performs a long-running action, it returns only when the action is complete, and the result can be returned. This pauses your program for the duration of the action. Multiple things can happen at the same time in an asynchronous model. Your program continues to run after you start an action. When the action is completed, the program is notified and has access to the result. 

An example to illustrate this concept better is with a waiter and a customer who wants some food. Let’s contrast this programming model with synchronous and asynchronous. 

Synchronous Model 

With the synchronous model, the waiter will come to the customer, ask for his order, send it to the kitchen, and then wait till it's done before bringing the food to the customer. 

This model blocks the waiter from doing any other thing until the food is ready and, as such, will be pretty much handicapping to scale. 

Asynchronous Model

With the asynchronous model, the waiter will come to the customer, take his order, send it to the kitchen, doesn’t wait until the food is done, but return and take more orders from other customers. 

Once the chef completes an order, the waiter will receive a signal to come to send the order to the customer who placed it. This will be repeated until the chef processes all the orders in the kitchen. This model is called non-blocking because it does not stop the waiter from taking more orders from the customers. 

Here is how to code this example using the synchronous model approach. 

console.log("Get order 1"); 
console.log("Send order 1 to kitchen"); 
console.log("Wait for food prep..."); 
console.log("Return order to customer 1"); 

We can see that the wait time must be honored with this approach. If the food isn’t prepared, the waiter can’t return the order to the customer. And if anything goes wrong in the process, then the entire task is jeopardized, and for sure, the customer will never get his order. 

Here is an alternate example of how to use the node.js async function… 

console.log("Get order 1"); 
console.log("Send order 1 to kitchen"); 
setTimeout(() => { 
  console.log("Wait for food prep..."); 
}, 2000); 
console.log("Return order to customer 1"); 

Here is the output of the above process… 

Our program did not wait for setTimeout() to complete before proceeding to the next line, returning to the function, and printing the output. This is known as a non-blocking code. 

In JavaScript, there are three design patterns for dealing with asynchronous programming:  

  • callbacks 
  • promises 
  • and async/await (just a syntactical sugar of promises) 

Join our full stack developer bootcamp to advance your software development skills. 

Callbacks in JavaScript 

In JavaScript, callbacks are an excellent way to handle asynchronous behavior. Because everything in JavaScript behaves like an object, functions have the type of object just like any other object, whether strings, arrays, or numbers. You can pass functions as arguments to other functions, which is the main idea behind callbacks. Here is an example… 

const placeOrder = (customerId, callback) => { 
  console.log("Preparing dish...") 
  setTimeout(() => { 
    console.log("Dish Prepared...") 
    callback({customerId: customerId, customerOrder: 'Pizza'}) 
  }, 2000); 
} 
placeOrder(1, (order) => console.log("Order", order)) 

And here is the result on the console… 

What is Callback Hell? 

A callback is simply a regular function called after another function has completed its execution. 

To elaborate, callback hell occurs when you have multiple asynchronous functions. Those functions rely on one another, which can get quite messy with the callback functions being nested in multiple layers. Here is a quick preview of how a callback hell is formulated. 

functionOne(args, function() { 
  functionTwo(args, function() { 
    functionThree(args, function() { 
      // functionEtc... 
    }); 
  }); 
}); 

This quickly results in chaos and renders the codes difficult to read and maintain. 

From our waiter example, we can quickly fall into a callback hell writing the code this way: 

cookFood(1, (order) => { 
  console.log("Food in preparation", order) 
  notifyWaiter(order.customerId, (order) => { 
    console.log("Waiter now with food", order) 
    serveCustomer(order.customerId, (order) => { 
      console.log("Customer's food served", order) 
      # This is some callback hell... 
    }) 
  }) 
}) 

You can see a lot of function nesting here and some scary code; this is what we call Callback Hell. 

What are Promises in JavaScript?  

Promises were introduced in the ES6 version in 2015. They are an alternative to callbacks for delivering more refined asynchronous computation results. 

Promises will require more effort from the programmer to implement but provide several benefits to the users. Their codes are more readable than callbacks, and they have many use-cases, such as with the fetch API in JavaScript, Firebase SDK, etc. 

In reality, promises have four states; below is a list of them. 

fulfilled - The action succeeded  

rejected - The action failed  

pending - Action yet to be fulfilled or rejected  

settled - Action fulfilled or rejected 

 

 Follow the example code below to understand how to create a promise.

const meetCustomer = (id) => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      console.log(`Waiter approached customer at table #${id}...`);
      resolve({ customerId: id });
    }, 2000);
  });
}
const getOrder = (id) => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      console.log(`Order Received for customer at table #${id}...`);
      resolve({ customerId: id, customerOrder: "Pizza" });
    }, 2000);
  });
}
const notifyWaiter = (id) => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      console.log(`Order for customer at table #${id} processed....`);
      resolve({ customerId: id, customerOrder: "Pizza" });
      // reject(new Error("Error occured with waiter"));
    }, 2000);
  });
}
const serveCustomer = (id) => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      console.log(`Customer with order number #${id} served...`);
      resolve({ customerId: id, customerOrder: "Pizza" });
    }, 2000);
  });
}

We wrote three functions; instead of passing a callback function, we now return a Promise with two arguments, resolve and reject. If everything goes well, we’ll call on the resolve; otherwise, the function calls on the reject action.

Now, let's look at how to consume promises…

# Replacing Callback with Promises to avoid callback hell
meetCustomer(1)
  .then((order) => getOrder(order.customerId))
  .then((order) => notifyWaiter(order.customerId))
  .then((order) => serveCustomer(order.customerId))
  .catch((err) => console.log("Error: ", err.message));

Now, this is what the result looks like…

And don't you think that's a lot more readable? This was streamlined by using arrow functions rather than simple functions. We avoided function nesting to reduce code complexity instead of the callback approach, which is how promises work. 

Async/Await 

As part of the ECMAScript 2017 JavaScript edition, JavaScript added async/await support in 2017. It's supposed to be a better way to write promises, and aids us in keeping our code simple and clean. 

It is dead simple to write a node.js async function. Just by simply writing the keyword async before any regular function converts it to a promise, isn’t that amazing? In other words, async/await is syntactic sugar for deposits. If you want to avoid chaining of then () methods in promises, you can use the async/await approach; regardless of this, it still uses chaining internally. 

See the example below to understand how to use the nodejs async function… 

// Async- await approach 
const runRestaurant = async (customerId) => { 
  const customer = await meetCustomer(customerId) 
  const order = await getOrder(customer.customerId) 
  await notifyWaiter(order.customerId) 
  await serveCustomer(order.customerId) 
console. log(`Order of customer fulfilled...`) 
} 
runRestaurant(1); 

As you can see, async await in node js is a lot more readable than promises. Every time we use await, we must decorate it with an async function. We don't have a catch() method in this case, so we're using a try-catch block for error handling. 

Handling Errors with Async/Await 

There are about three ways to handle errors with async await in node js properly, and they include: 

Promise Rejection Method

To see how to use the promise rejection method when using async/await, let’s integrate it into the runRestaurant() function.

const runRestaurant = async (customerId) => { 
  if(!!!customerId)  
    await Promise.reject(new Error("Invalid CustomerId!")) 
 const customer = await meetCustomer(customerId) 
  const order = await getOrder(customer.customerId) 
  await notifyWaiter(order.customerId) 
  await serveCustomer(order.customerId) 
} 
runRestaurant(1) 
  .then(() => console.log(`Order of customer fulfilled...`)) 
  .catch((error) => console.log(error)) 

Now, this is how our function works with a promise rejection method… 

Error Throwing Method 

Using the throw error method will have our function look like this: 

const runRestaurant = async (customerId) => { 
  if(!!!customerId)  
    throw new Error("Invalid CustomerId!") 
const customer = await meetCustomer(customerId); 
  const order = await getOrder(customer.customerId); 
  await notifyWaiter(order.customerId); 
  await serveCustomer(order.customerId); 
} 
runRestaurant(1) 
  .then(() => console.log(`Order of customer fulfilled...`)) 
  .catch((error) => console.log(error)) 
There is no need to use the await modifier and the throw new Error(). 

The Try-catch Method 

Lastly, for handling an error with async await nodejs, we have the tryCatch() method, which has a more streamlined approach compared to the others. 

const runRestaurant = async (customerId) => { 
  if(!!!customerId) throw new Error("Invalid CustomerId!") 
try { 
    const customer = await meetCustomer(customerId); 
    const order = await getOrder(customer.customerId); 
    await notifyWaiter(order.customerId); 
    await serveCustomer(order.customerId); 
  } catch (err) { 
    console.log("Error: ", err.message); 
  } 
} 
runRestaurant(1) 
  .then(() => console.log(`Order of customer fulfilled...`)) 
  .catch((error) => console.log(error)) 

I think that the tryCatch() method is the smoothest of them. Now let’s see some test cases for passing an accurate customerId (1 and above).  

Conclusion

We’ve come to the end of this tutorial. Hopefully, you got a bunch of insights on how to use callbacks, promises, and async/await functions for your next project.

If you want to improve your software development skills quickly, kindly check out knowledgeHut’s Node.js course online. 

Till next time, have a great day!!!

Frequently Asked Questions (FAQs)

Q. How can I use await in Node.js?

You can then use await within the function's body by specifying the async keyword at the beginning of a function.

Q. What is await in Node.js?

Await is a promise-based operator used together with a nodejs async functions.

Q. Is it possible to use await without async?

No, the await operator is only useful in async functions.

Q. Can I use await with promises only?

To wait for a Promise, use the await operator. It can only be used within an async function, but it can also be used independently with JavaScript modules.

Q. Do await the stop thread?

The thread is not blocked by await. Node.js continues to run all JavaScript as a single-threaded process.

Profile

Darlington Gospel

Blog Author

I am a Software Engineer skilled in JavaScript and Blockchain development. You can reach me on LinkedIn, Facebook, Github, or on my website.

Want to become a sought-after web developer?

Avail your free 1:1 mentorship session.

Select
Your Message (Optional)