Node js - Async/Await

Node.js - Async/Await

Async/Await in Node.js

Introduction

Async/Await is a powerful and modern approach to handle asynchronous operations in Node.js. Introduced in ECMAScript 2017 (ES8), it simplifies the process of writing asynchronous code and makes it look synchronous, enhancing readability and maintainability. In this document, we will explore the fundamentals of Async/Await in Node.js, its syntax, behavior, practical applications, and best practices.

Background: The Evolution of Asynchronous Handling

1. Callbacks

Callbacks were the traditional way of handling asynchronous operations in Node.js. However, they often led to deeply nested structures known as "callback hell".


const fs = require('fs');

fs.readFile('file.txt', 'utf8', function(err, data) {
    if (err) {
        return console.error(err);
    }
    console.log(data);
});

2. Promises

Promises were introduced to solve callback hell by chaining asynchronous operations.


const fs = require('fs').promises;

fs.readFile('file.txt', 'utf8')
    .then(data => console.log(data))
    .catch(err => console.error(err));

3. Async/Await

Async/Await further simplifies asynchronous programming by allowing us to write asynchronous code as if it were synchronous.


const fs = require('fs').promises;

async function readFile() {
    try {
        const data = await fs.readFile('file.txt', 'utf8');
        console.log(data);
    } catch (err) {
        console.error(err);
    }
}

readFile();

Understanding Async Functions

An async function is a function declared with the async keyword. It always returns a Promise. If the function returns a value, the Promise will be resolved with that value. If the function throws an error, the Promise will be rejected.

Syntax


async function functionName() {
    // Function body
}

Example


async function greet() {
    return "Hello, Async!";
}

greet().then(message => console.log(message));

The await Keyword

The await keyword can only be used inside async functions. It pauses the execution of the function until the Promise is resolved or rejected.

Example


async function fetchData() {
    const result = await Promise.resolve("Data received");
    console.log(result);
}

fetchData();

Error Handling with Async/Await

Errors in async functions can be caught using try...catch blocks.


async function fetchData() {
    try {
        const result = await Promise.reject("Something went wrong");
        console.log(result);
    } catch (error) {
        console.error("Caught error:", error);
    }
}

fetchData();

Chaining Asynchronous Calls


function delay(ms) {
    return new Promise(resolve => setTimeout(resolve, ms));
}

async function chainCalls() {
    await delay(1000);
    console.log("First call done");

    await delay(1000);
    console.log("Second call done");

    await delay(1000);
    console.log("Third call done");
}

chainCalls();

Using Async/Await with APIs

Example: Fetching JSON data


const https = require('https');

function getJSON(url) {
    return new Promise((resolve, reject) => {
        https.get(url, res => {
            let data = '';

            res.on('data', chunk => {
                data += chunk;
            });

            res.on('end', () => {
                try {
                    resolve(JSON.parse(data));
                } catch (err) {
                    reject(err);
                }
            });
        }).on('error', reject);
    });
}

async function fetchUser() {
    try {
        const user = await getJSON('https://jsonplaceholder.typicode.com/users/1');
        console.log(user);
    } catch (err) {
        console.error("Fetch failed:", err.message);
    }
}

fetchUser();

Sequential vs Parallel Execution

Sequential Execution


async function runSequential() {
    const a = await Promise.resolve("First");
    const b = await Promise.resolve("Second");
    const c = await Promise.resolve("Third");

    console.log(a, b, c);
}

runSequential();

Parallel Execution


async function runParallel() {
    const [a, b, c] = await Promise.all([
        Promise.resolve("First"),
        Promise.resolve("Second"),
        Promise.resolve("Third")
    ]);

    console.log(a, b, c);
}

runParallel();

Using Async/Await in Loops

You can use async/await inside loops, but be aware that forEach does not support async/await properly.

Incorrect Usage


const list = [1, 2, 3];

list.forEach(async (item) => {
    await delay(1000);
    console.log(item);
});

Correct Usage with for...of


async function loopAsync() {
    const list = [1, 2, 3];

    for (let item of list) {
        await delay(1000);
        console.log(item);
    }
}

loopAsync();

Returning Values from Async Functions


async function getValue() {
    return 42;
}

getValue().then(value => console.log("Returned:", value));

Async/Await with Database Queries


const mysql = require('mysql2/promise');

async function connectDb() {
    try {
        const connection = await mysql.createConnection({
            host: 'localhost',
            user: 'root',
            database: 'test'
        });

        const [rows] = await connection.execute('SELECT * FROM users');
        console.log(rows);
    } catch (err) {
        console.error("DB Error:", err.message);
    }
}

connectDb();

Handling Multiple Errors


async function runTasks() {
    try {
        const result1 = await Promise.reject("Error 1");
        const result2 = await Promise.reject("Error 2");
    } catch (err) {
        console.error("Caught error:", err);
    }
}

runTasks();

Nested Async Functions


async function outer() {
    async function inner() {
        return "Inner done";
    }

    const result = await inner();
    console.log(result);
}

outer();

Custom Awaitable Objects


class Custom {
    then(resolve, reject) {
        setTimeout(() => resolve("Resolved custom object"), 1000);
    }
}

async function useCustom() {
    const result = await new Custom();
    console.log(result);
}

useCustom();

Common Mistakes with Async/Await

  • Using await outside async function
  • Not handling errors with try/catch
  • Awaiting Promises that could run in parallel
  • Using async inside forEach

Best Practices

  • Use async/await for better code readability
  • Always wrap await statements in try/catch
  • Use Promise.all for parallel async operations
  • Refactor nested promises into async/await chains
  • Keep functions short and focused

Async/Await vs Promises

Aspect Promises Async/Await
Readability Moderate High
Error Handling .catch() try/catch
Syntax Chained Flat & sequential
Debugging Stack traces harder to follow More intuitive stack trace

Use Cases for Async/Await

  • Database operations
  • API requests
  • File I/O operations
  • Sequential task execution
  • Batch job processing

Future of Async Programming in Node.js

With the introduction of async iterators, top-level await (in ES2022), and more robust debugging tools, asynchronous programming in Node.js continues to evolve. Async/await has become the default and preferred way to handle asynchronous operations in modern Node.js applications.

Async/Await revolutionized asynchronous programming in JavaScript and Node.js. It provides a clean, concise, and readable way to write asynchronous code, minimizing complexity and errors often found in callback- or promise-based code. Async functions return Promises by default and integrate seamlessly with modern JavaScript features.

Whether you're accessing APIs, performing file or database operations, or simply waiting for timers, async/await can streamline your code, make it easier to follow, and reduce the chances of introducing bugs. Understanding how to use it properlyβ€”and when not to use itβ€”is crucial for writing effective, scalable Node.js applications.

Beginner 5 Hours
Node.js - Async/Await

Async/Await in Node.js

Introduction

Async/Await is a powerful and modern approach to handle asynchronous operations in Node.js. Introduced in ECMAScript 2017 (ES8), it simplifies the process of writing asynchronous code and makes it look synchronous, enhancing readability and maintainability. In this document, we will explore the fundamentals of Async/Await in Node.js, its syntax, behavior, practical applications, and best practices.

Background: The Evolution of Asynchronous Handling

1. Callbacks

Callbacks were the traditional way of handling asynchronous operations in Node.js. However, they often led to deeply nested structures known as "callback hell".

const fs = require('fs'); fs.readFile('file.txt', 'utf8', function(err, data) { if (err) { return console.error(err); } console.log(data); });

2. Promises

Promises were introduced to solve callback hell by chaining asynchronous operations.

const fs = require('fs').promises; fs.readFile('file.txt', 'utf8') .then(data => console.log(data)) .catch(err => console.error(err));

3. Async/Await

Async/Await further simplifies asynchronous programming by allowing us to write asynchronous code as if it were synchronous.

const fs = require('fs').promises; async function readFile() { try { const data = await fs.readFile('file.txt', 'utf8'); console.log(data); } catch (err) { console.error(err); } } readFile();

Understanding Async Functions

An async function is a function declared with the async keyword. It always returns a Promise. If the function returns a value, the Promise will be resolved with that value. If the function throws an error, the Promise will be rejected.

Syntax

async function functionName() { // Function body }

Example

async function greet() { return "Hello, Async!"; } greet().then(message => console.log(message));

The await Keyword

The await keyword can only be used inside async functions. It pauses the execution of the function until the Promise is resolved or rejected.

Example

async function fetchData() { const result = await Promise.resolve("Data received"); console.log(result); } fetchData();

Error Handling with Async/Await

Errors in async functions can be caught using try...catch blocks.

async function fetchData() { try { const result = await Promise.reject("Something went wrong"); console.log(result); } catch (error) { console.error("Caught error:", error); } } fetchData();

Chaining Asynchronous Calls

function delay(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } async function chainCalls() { await delay(1000); console.log("First call done"); await delay(1000); console.log("Second call done"); await delay(1000); console.log("Third call done"); } chainCalls();

Using Async/Await with APIs

Example: Fetching JSON data

const https = require('https'); function getJSON(url) { return new Promise((resolve, reject) => { https.get(url, res => { let data = ''; res.on('data', chunk => { data += chunk; }); res.on('end', () => { try { resolve(JSON.parse(data)); } catch (err) { reject(err); } }); }).on('error', reject); }); } async function fetchUser() { try { const user = await getJSON('https://jsonplaceholder.typicode.com/users/1'); console.log(user); } catch (err) { console.error("Fetch failed:", err.message); } } fetchUser();

Sequential vs Parallel Execution

Sequential Execution

async function runSequential() { const a = await Promise.resolve("First"); const b = await Promise.resolve("Second"); const c = await Promise.resolve("Third"); console.log(a, b, c); } runSequential();

Parallel Execution

async function runParallel() { const [a, b, c] = await Promise.all([ Promise.resolve("First"), Promise.resolve("Second"), Promise.resolve("Third") ]); console.log(a, b, c); } runParallel();

Using Async/Await in Loops

You can use async/await inside loops, but be aware that forEach does not support async/await properly.

Incorrect Usage

const list = [1, 2, 3]; list.forEach(async (item) => { await delay(1000); console.log(item); });

Correct Usage with for...of

async function loopAsync() { const list = [1, 2, 3]; for (let item of list) { await delay(1000); console.log(item); } } loopAsync();

Returning Values from Async Functions

async function getValue() { return 42; } getValue().then(value => console.log("Returned:", value));

Async/Await with Database Queries

const mysql = require('mysql2/promise'); async function connectDb() { try { const connection = await mysql.createConnection({ host: 'localhost', user: 'root', database: 'test' }); const [rows] = await connection.execute('SELECT * FROM users'); console.log(rows); } catch (err) { console.error("DB Error:", err.message); } } connectDb();

Handling Multiple Errors

async function runTasks() { try { const result1 = await Promise.reject("Error 1"); const result2 = await Promise.reject("Error 2"); } catch (err) { console.error("Caught error:", err); } } runTasks();

Nested Async Functions

async function outer() { async function inner() { return "Inner done"; } const result = await inner(); console.log(result); } outer();

Custom Awaitable Objects

class Custom { then(resolve, reject) { setTimeout(() => resolve("Resolved custom object"), 1000); } } async function useCustom() { const result = await new Custom(); console.log(result); } useCustom();

Common Mistakes with Async/Await

  • Using await outside async function
  • Not handling errors with try/catch
  • Awaiting Promises that could run in parallel
  • Using async inside forEach

Best Practices

  • Use async/await for better code readability
  • Always wrap await statements in try/catch
  • Use Promise.all for parallel async operations
  • Refactor nested promises into async/await chains
  • Keep functions short and focused

Async/Await vs Promises

Aspect Promises Async/Await
Readability Moderate High
Error Handling .catch() try/catch
Syntax Chained Flat & sequential
Debugging Stack traces harder to follow More intuitive stack trace

Use Cases for Async/Await

  • Database operations
  • API requests
  • File I/O operations
  • Sequential task execution
  • Batch job processing

Future of Async Programming in Node.js

With the introduction of async iterators, top-level await (in ES2022), and more robust debugging tools, asynchronous programming in Node.js continues to evolve. Async/await has become the default and preferred way to handle asynchronous operations in modern Node.js applications.

Async/Await revolutionized asynchronous programming in JavaScript and Node.js. It provides a clean, concise, and readable way to write asynchronous code, minimizing complexity and errors often found in callback- or promise-based code. Async functions return Promises by default and integrate seamlessly with modern JavaScript features.

Whether you're accessing APIs, performing file or database operations, or simply waiting for timers, async/await can streamline your code, make it easier to follow, and reduce the chances of introducing bugs. Understanding how to use it properly—and when not to use it—is crucial for writing effective, scalable Node.js applications.

Related Tutorials

Frequently Asked Questions for Node.js

A function passed as an argument and executed later.

Runs multiple instances to utilize multi-core systems.

Reusable blocks of code, exported and imported using require() or import.

nextTick() executes before setImmediate() in the event loop.

Starts a server and listens on specified port.

Node Package Manager β€” installs, manages, and shares JavaScript packages.

A minimal and flexible web application framework for Node.js.

A stream handles reading or writing data continuously.

It processes asynchronous callbacks and non-blocking I/O operations efficiently.

Node.js is a JavaScript runtime built on Chrome's V8 engine for server-side scripting.

An object representing the eventual completion or failure of an asynchronous operation.

require is CommonJS; import is ES6 syntax (requires transpilation or newer versions).

Use module.exports or exports.functionName.

Variables stored outside the code for configuration, accessed using process.env.


MongoDB, often used with Mongoose for schema management.

Describes project details and manages dependencies and scripts.

Synchronous blocks execution; asynchronous runs in background without blocking.

Allows or restricts resources shared between different origins.

Use try-catch, error events, or middleware for error handling.

Provides file system-related operations like read, write, delete.

Using event-driven architecture and non-blocking I/O.

Functions in Express that execute during request-response cycle.

A set of routes or endpoints to interact with server logic or databases.

Yes, it's single-threaded but handles concurrency using the event loop and asynchronous callbacks.

Middleware to parse incoming request bodies, like JSON or form data.

line

Copyrights © 2024 letsupdateskills All rights reserved