Table of contents
1.
Introduction
2.
What are Async Functions in Node.js?
3.
Handling Asynchronous Operations Before Async Await
4.
Callbacks
4.1.
Example of a Callback
4.2.
Problems with Callbacks
5.
Promises
5.1.
Example: Before async/await
5.2.
Advantages of Promises
6.
Async and Await in Node.js
6.1.
How Async/Await Works
6.2.
Example
7.
How Async/Await Works?
7.1.
Example: Sequential Execution
7.2.
Example: Concurrent Execution
7.3.
Key Advantages of Async/Await
8.
Frequently Asked Questions
8.1.
What is the main difference between callbacks and async/await?
8.2.
Can we use async/await without promises?
8.3.
What happens if we forget the await keyword?
9.
Conclusion
Last Updated: Jan 31, 2025
Medium

Async and Await in Node.js

Author Rahul Singh
0 upvote
Career growth poll
Do you think IIT Guwahati certified course can help you in your career?

Introduction

Async and Await in Node.js are essential features that help in managing asynchronous operations easily. They provide a simpler and more readable way to handle promises, avoiding complex callback functions and .then() chaining. The async keyword is used to define an asynchronous function, and the await keyword pauses the execution until a promise is resolved. This approach makes it easier to handle tasks like API calls, database queries, and file operations in Node.js.

Async and Await in Node.js

In this article, you will learn about async and await in Node.js, their advantages, and practical examples to use them efficiently in your applications.

What are Async Functions in Node.js?

Async functions in Node.js are a way to handle asynchronous operations more easily. They allow you to write code that looks like it's running synchronously, but it's actually doing things in the background. This makes your code easier to read and understand.

To use async functions, you need to declare a function with the async keyword. This tells Node.js that the function will be handling asynchronous operations. For example:

async function fetchData() {
    return "Data fetched successfully!";
}
fetchData().then(data => {
    console.log(data);
});


In this example, fetchData is an async function. When you call it, it returns a promise. The method is used to handle the resolved value of the promise.

Handling Asynchronous Operations Before Async Await

Before the introduction of async and await, handling asynchronous operations in Node.js relied heavily on callbacks and promises. While these approaches worked, they often led to issues like "callback hell" or made the code harder to read and debug.

Callbacks

A callback is a function passed as an argument to another function. It gets executed after the asynchronous operation is complete. This was the primary method for handling asynchronous tasks in the early days of Node.js.

Example of a Callback

const fs = require('fs');
// Reading a file using callbacks
fs.readFile('example.txt', 'utf8', (err, data) => {
    if (err) {
        console.error('Error reading the file:', err);
        return;
    }
    console.log('File content:', data);
});


Explanation:

  • The fs.readFile method is asynchronous.
     
  • A callback function is passed as an argument to handle the result or error once the file is read.
     
  • If there is an error, it is handled inside the callback function.

Problems with Callbacks

  1. Callback Hell: When multiple asynchronous operations are nested, the code becomes difficult to read and maintain.
     
  2. Error Handling: Managing errors across multiple nested callbacks can be tedious.

Promises

Promises were introduced to overcome the issues of callbacks. A promise represents a value that may be available now, in the future, or never. It provides a cleaner way to chain asynchronous operations using .then() and .catch().

Example: Before async/await

const fs = require('fs').promises;
// Reading a file using promises
fs.readFile('example.txt', 'utf8')
    .then(data => {
        console.log('File content:', data);
    })
    .catch(err => {
        console.error('Error reading the file:', err);
    });


Explanation:

  • The fs.promises.readFile method returns a promise.
     
  • Using .then(), we can access the file content.
     
  • Using .catch(), we handle any errors that occur during the file read operation.

Advantages of Promises

  1. Chaining: Promises allow chaining multiple asynchronous operations, making the code more readable.
     
  2. Error Propagation: Errors can be caught in one .catch() block for the entire chain.

Async and Await in Node.js

The async and await in node.js keywords were introduced in ES2017 (ES8). They provide a way to work with promises in a more synchronous-like manner, making the code easier to read and write.

How Async/Await Works

  1. Async Functions:
    • Declared using the async keyword.
       
    • Always return a promise.
       
  2. Await Keyword:
    • Used to pause the execution of an async function until the promise is resolved or rejected.
       
    • Can only be used inside an async function.

Example

const fs = require('fs').promises;
// Reading a file using async/await
async function readFileContent() {
    try {
        const data = await fs.readFile('example.txt', 'utf8');
        console.log('File content:', data);
    } catch (err) {
        console.error('Error reading the file:', err);
    }
}
readFileContent();


Explanation:

  • The async keyword is added to the function declaration.
     
  • The await keyword is used before the fs.readFile call to pause execution until the promise resolves.
     
  • The try...catch block handles errors.

How Async/Await Works?

To better understand how async and await in node.js work, let's break it down:

  1. Synchronous-Like Flow:
    • When you use await, the code looks synchronous, but under the hood, it is non-blocking. This ensures that other parts of the application continue running.
       
  2. Error Handling:
    • With try...catch, you can handle errors in a clean and organized way.
       
  3. Combining Multiple Async Calls:
    • You can combine multiple await calls and execute them sequentially or concurrently using techniques like Promise.all.

Example: Sequential Execution

const fs = require('fs').promises;
async function processFiles() {
    try {
        const file1 = await fs.readFile('file1.txt', 'utf8');
        console.log('File 1 content:', file1);


        const file2 = await fs.readFile('file2.txt', 'utf8');
        console.log('File 2 content:', file2);
    } catch (err) {
        console.error('Error processing files:', err);
    }
}
processFiles();


Explanation

  • The files are read one after another.
     
  • Each await pauses execution until the current file read is complete.

Example: Concurrent Execution

const fs = require('fs').promises;
async function processFilesConcurrently() {
    try {
        const [file1, file2] = await Promise.all([
            fs.readFile('file1.txt', 'utf8'),
            fs.readFile('file2.txt', 'utf8')
        ]);
        console.log('File 1 content:', file1);
        console.log('File 2 content:', file2);
    } catch (err) {
        console.error('Error processing files:', err);
    }
}
processFilesConcurrently();


Explanation:

  • The Promise.all method allows both file reads to happen concurrently.
     
  • The results are returned as an array when all promises are resolved.

Key Advantages of Async/Await

  1. Improved Readability: Code looks more like traditional synchronous code.
     
  2. Better Error Handling: Errors can be handled using try...catch, making it cleaner.
     
  3. Composability: Easily combine multiple async operations.
     
  4. Debugging: Easier to debug due to the synchronous-like structure.

Frequently Asked Questions

What is the main difference between callbacks and async/await?

Callbacks rely on passing functions to handle asynchronous operations, often resulting in callback hell. Async/await simplifies the syntax, making asynchronous code easier to read and manage.

Can we use async/await without promises?

No, async and await in node.js are built on top of promises. They are syntactic sugar to make promise-based code more readable.

What happens if we forget the await keyword?

If you forget the await keyword, the async function will not wait for the promise to resolve. It will immediately return a pending promise instead of the resolved value.

Conclusion

In this article, we learned about async and await in Node.js and how they simplify asynchronous programming. These keywords make it easier to handle promises, improving code readability and maintainability by eliminating callback nesting and complex .then() chains. Understanding and implementing these features correctly will help you build faster, more responsive, and scalable Node.js applications.

Live masterclass