Paramclasses Academy - Blog

Tutorials Details

←
Post 12 of 40
→

Node.js Asynchronous Programming: Callbacks, Promises and Async/Await

Node.js Asynchronous Programming: Callbacks, Promises and Async/Await

October 29, 2026

✦ Node.js Asynchronous Programming: Callbacks, Promises and Async/Await

❖ Introduction

When developing modern web applications, a program often needs to perform tasks such as reading files, fetching data from an API, connecting to a database, or sending emails. These operations can take time to complete.

If a program waits for every task to finish before moving to the next one, the application may become slow and unresponsive. This is where asynchronous programming in Node.js becomes important.

Node.js supports asynchronous programming through callbacks, Promises, and async/await. These approaches help developers handle operations that take time without unnecessarily blocking the main JavaScript execution thread.

In this tutorial, we will learn what asynchronous programming is, how callbacks work, how to use Promises, and how async/await makes asynchronous code easier to read. We will also explore practical examples for beginners.

✦ What Is Asynchronous Programming in Node.js?

Asynchronous programming allows a program to start an operation and continue executing other code while waiting for that operation to complete.

For example, imagine you are building a student management system. When a user requests student information, your application may need to retrieve records from a database. Instead of blocking all other work while waiting, Node.js can continue processing other tasks.

Consider this example:

console.log("Start");

setTimeout(() => {
    console.log("Task completed");
}, 2000);

console.log("End");

Output:

Start
End
Task completed

The message "Task completed" appears after approximately two seconds. Meanwhile, JavaScript continues executing the next statement.

This is a simple demonstration of asynchronous behavior in Node.js.

❖ Why Is Asynchronous Programming Important?

Asynchronous programming is useful because it helps applications handle waiting periods efficiently.

Important benefits include:

  • Better responsiveness: The application can continue doing other work while waiting for an operation.

  • Efficient I/O handling: Useful for file operations, network requests, and database queries.

  • Improved scalability: Node.js can manage many concurrent I/O operations without creating a separate JavaScript thread for each request.

  • Cleaner workflows: Promises and async/await make it easier to organize complex asynchronous operations.

  • Better user experience: Web applications can respond more efficiently when handling multiple requests.

Remember that asynchronous programming does not automatically make every task faster. CPU-intensive JavaScript can still block the event loop.

✦ 1. Understanding Callbacks in Node.js

A callback is a function passed to another function so that it can be called later, often when an operation completes.

Callbacks are one of the traditional ways of handling asynchronous operations in JavaScript.

❖ Simple Callback Example

function greetStudent(name, callback) {
    console.log("Hello, " + name);

    callback();
}

function showMessage() {
    console.log("Welcome to Param Classes!");
}

greetStudent("Rahul", showMessage);

Output:

Hello, Rahul
Welcome to Param Classes!

Here, showMessage is passed as a callback to greetStudent. The callback executes after the greeting is displayed.

❖ Asynchronous Callback Example

Node.js provides the File System module for working with files. Its callback-based methods can perform file operations asynchronously.

Create a file named student.txt:

Rahul
Course: Node.js

Now create callback.js:

const fs = require("fs");

fs.readFile("student.txt", "utf8", (err, data) => {
    if (err) {
        console.error("Error reading file:", err.message);
        return;
    }

    console.log(data);
});

console.log("Reading student information...");

Run the program:

node callback.js

The message Reading student information... may appear before the file contents because readFile() uses an asynchronous callback.

Important: Always handle the error argument. In Node.js callback APIs, the common pattern is (err, result), where err contains an error if the operation fails.

❖ What Is Callback Hell?

Callback Hell happens when many asynchronous operations are nested inside one another.

For example:

getStudent(function(student) {
    getCourse(student.id, function(course) {
        getResult(course.id, function(result) {
            console.log(result);
        });
    });
});

As the number of operations increases, the code can become difficult to read, maintain, and debug.

Promises and async/await provide cleaner alternatives for many such situations.

✦ 2. Understanding Promises in Node.js

A Promise represents the eventual result of an asynchronous operation. It can be in one of three states:

  • Pending: The operation has not finished.

  • Fulfilled: The operation completed successfully.

  • Rejected: The operation failed.

Promises commonly use .then() to handle successful results and .catch() to handle errors.

❖ Creating a Promise

const studentPromise = new Promise((resolve, reject) => {
    const marks = 75;

    if (marks >= 40) {
        resolve("Student passed!");
    } else {
        reject(new Error("Student failed!"));
    }
});

studentPromise
    .then((message) => {
        console.log(message);
    })
    .catch((error) => {
        console.error(error.message);
    });

Output:

Student passed!

In this example, resolve() fulfills the Promise, while reject() rejects it. The .then() handler receives the successful result, and .catch() handles a rejection.

❖ Using Promises with a Delay

function getStudentData() {
    return new Promise((resolve) => {
        setTimeout(() => {
            resolve({
                name: "Rahul",
                course: "Node.js"
            });
        }, 2000);
    });
}

getStudentData()
    .then((student) => {
        console.log(student);
    })
    .catch((error) => {
        console.error(error.message);
    });

After approximately two seconds, the student object is displayed.

Promises help avoid deeply nested callbacks and make it easier to chain multiple asynchronous operations.

❖ Promise Chaining

Promise.resolve(10)
    .then((number) => number * 2)
    .then((number) => number + 5)
    .then((result) => {
        console.log(result);
    })
    .catch((error) => {
        console.error(error.message);
    });

Output:

25

Each .then() receives the previous handler's returned value. If an error is thrown or a Promise is rejected, the chain can be handled by .catch().

✦ 3. Understanding Async/Await in Node.js

Async/await is syntax built on top of Promises. It makes asynchronous code look more like regular, sequential JavaScript.

  • async declares a function that returns a Promise.

  • await waits for a Promise to settle before continuing inside that async function.

  • try/catch can be used to handle errors from awaited operations.

❖ Basic Async/Await Example

function getStudent() {
    return new Promise((resolve) => {
        setTimeout(() => {
            resolve("Rahul");
        }, 2000);
    });
}

async function showStudent() {
    const name = await getStudent();

    console.log("Student Name:", name);
}

showStudent();
console.log("Program continues...");

The message Program continues... appears while the async function is waiting. After the Promise resolves, the student's name is displayed.

await pauses the execution of showStudent() at that point; it does not block the entire Node.js process.

❖ Async/Await with Error Handling

function getStudentMarks() {
    return new Promise((resolve, reject) => {
        const marks = 85;

        if (marks >= 0 && marks <= 100) {
            resolve(marks);
        } else {
            reject(new Error("Invalid marks"));
        }
    });
}

async function displayMarks() {
    try {
        const marks = await getStudentMarks();

        console.log("Student Marks:", marks);
    } catch (error) {
        console.error("Error:", error.message);
    }
}

displayMarks();

This structure is often easier to understand than a long chain of .then() handlers, particularly when multiple operations depend on one another.

✦ 4. Practical Example: Read a File Using Async/Await

Let's use Node.js to read student information from a file with the Promise-based File System API.

Create student.txt:

Student: Rahul
Course: Node.js

Create async-file.js:

const fs = require("fs/promises");

async function readStudentFile() {
    try {
        const data = await fs.readFile("student.txt", "utf8");

        console.log("Student Information:");
        console.log(data);
    } catch (error) {
        console.error("Unable to read file:", error.message);
    }
}

readStudentFile();

Run the program:

node async-file.js

This example uses fs/promises, which provides Promise-based file operations. If the file does not exist or cannot be read, the catch block handles the error.

❖ 5. Callbacks vs Promises vs Async/Await

Feature Callbacks Promises Async/Await
Basic approach Pass a function Use .then() and .catch() Use async and await
Readability Can become nested Supports chaining Often easiest to read
Error handling Commonly uses err Uses rejection handlers Uses try/catch
Best use Existing callback APIs Promise-based workflows Sequential asynchronous logic

 

All three approaches remain useful. Async/await is often a good starting point for new beginner projects, but understanding callbacks and Promises is essential because you will encounter them in existing Node.js libraries and applications.

✦ 6. Running Independent Tasks with Promise.all()

Sometimes, two asynchronous operations do not depend on each other. In that case, running them concurrently can be more efficient than awaiting them one by one.

function getStudent() {
    return new Promise((resolve) => {
        setTimeout(() => resolve("Rahul"), 1000);
    });
}

function getCourse() {
    return new Promise((resolve) => {
        setTimeout(() => resolve("Node.js"), 1000);
    });
}

async function showDetails() {
    try {
        const [student, course] = await Promise.all([
            getStudent(),
            getCourse()
        ]);

        console.log("Student:", student);
        console.log("Course:", course);
    } catch (error) {
        console.error("Unable to load details:", error.message);
    }
}

showDetails();

Both operations start when Promise.all() receives them. If both complete successfully, their results are returned in the same order as the input array. If any Promise rejects, Promise.all() rejects.

Use this pattern when tasks are independent. If one task requires the result of another, await them in the appropriate sequence instead.

✦ 7. Common Mistakes Beginners Should Avoid

❖ Forgetting to handle errors

Use callback error checks, .catch(), or try/catch with awaited Promises.

❖ Forgetting await

If you forget to await a Promise, you may log the Promise itself or use a value before the operation finishes.

❖ Using await outside the correct context

In standard CommonJS files, place await inside an async function. Top-level await is supported in appropriate ES module contexts.

❖ Awaiting independent operations one after another

If operations do not depend on each other, consider Promise.all() to run them concurrently.

❖ Assuming async code cannot block

An asynchronous function can still run CPU-intensive JavaScript that blocks the event loop. For heavy computation, consider worker threads or a separate processing service.

✦ Why Learn Node.js at Param Classes?

Understanding asynchronous programming is an important step toward developing APIs, database-driven applications, real-time systems, and backend services.

At Param Classes, learners can build their programming foundation through practical examples and project-oriented learning. Practising callbacks, Promises, async/await, file handling, Express.js, and database integration helps students understand how real Node.js applications work.

Explore more learning resources on the Param Classes website. For website development and digital solutions, visit ParamWebInfo.

❖ Frequently Asked Questions (FAQs)

1. What is asynchronous programming in Node.js?

Asynchronous programming allows Node.js to handle operations such as file access and network requests without waiting idly for each operation to finish before executing other work.

2. What is a callback in Node.js?

A callback is a function passed to another function so it can be executed later, often after an operation completes.

3. What is a Promise in JavaScript?

A Promise represents the future success or failure of an asynchronous operation. It can be pending, fulfilled, or rejected.

4. What is the difference between Promises and async/await?

Async/await uses Promises underneath but provides syntax that often makes asynchronous workflows easier to read.

5. Is async/await better than callbacks?

For many multi-step workflows, async/await is easier to maintain. Callbacks are still important for understanding older code and callback-based APIs.

6. Can Node.js handle multiple requests asynchronously?

Yes. Node.js can handle many concurrent I/O operations using its event-driven architecture. CPU-heavy work can still block the event loop if it is not handled appropriately.

✦ Conclusion

Asynchronous programming is a core concept in Node.js development. Callbacks introduce the basic idea of executing functions after an operation, Promises provide structured success and failure handling, and async/await makes Promise-based code easier to read.

Start by practising the examples in this tutorial. Then try reading files, calling APIs, and connecting a Node.js application to a database. These practical exercises will help you build a stronger foundation for backend development.

Keep learning and keep building with Param Classes!

←
Post 12 of 40
→