Mastering Promises and Async/Await in Node.js

 

Welcome to the world of asynchronous programming in Node.js!

Imagine you walk into a coffee shop.

  • You order a latte and the barista takes your order.
  • But instead of waiting there like a statue, you go find a seat and browse your phone.
  • When the latte is ready, they call your name, and you go pick it up.

This is how Promises and Async/Await work in Node.js!

In this guide, you’ll learn:

  • What Promises are and how they solve Callback Hell.
  • How to use Async/Await to make your code cleaner and more readable.
  • Real-world examples of Promises and Async/Await in action.

Let’s get brewing! 

 

The Problem: Callback Hell

Before Promises, JavaScript handled async operations using callbacks. But too many callbacks lead to Callback Hell (a.k.a. "Pyramid of Doom").

Example of Callback Hell:

getUser(1, function(user) {
    getOrders(user.id, function(orders) {
        getOrderDetails(orders[0], function(details) {
            getShippingInfo(details, function(shipping) {
                console.log("Shipping Info:", shipping);
            });
        });
    });
});

Looks messy and hard to read, right?

Enter Promises – our savior! 

 

Understanding Promises in Node.js

A Promise is like a contract that says:
"I promise to give you the result when I’m done. Either I succeed  or I fail."

A Promise can be in one of three states:

  • 1️Pending – Waiting for the result.
  • 2️Resolved (Fulfilled) – Success!
  • 3️Rejected – Something went wrong.

Creating a Promise

const orderPizza = (flavor) => {
    return new Promise((resolve, reject) => {
        console.log(`Ordering a ${flavor} pizza... `);

        setTimeout(() => {
            if (flavor === "Hawaiian") {
                reject("Sorry, we don’t serve pineapple on pizza! ");
            } else {
                resolve(`${flavor} pizza is ready!`);
            }
        }, 3000);
    });
};

Handling a Promise with .then() and .catch()

orderPizza("Pepperoni")
    .then((message) => {
        console.log(message); // Success 
    })
    .catch((error) => {
        console.log("Error:", error); // Failure 
    });

Output:

Ordering a Pepperoni pizza... 
(Pizza is being prepared...)
Pepperoni pizza is ready!

But if we order "Hawaiian"

Output:

Ordering a Hawaiian pizza... 
Error: Sorry, we don’t serve pineapple on pizza! 

Explanation:

  • If the order succeeds, we call resolve(), and .then() executes.
  • If it fails, we call reject(), and .catch() handles the error.

 

Chaining Promises (No More Callback Hell!)

Let's say we have a food delivery system:

function getUser(userId) {
    return new Promise(resolve => {
        setTimeout(() => resolve({ id: userId, name: "Alice" }), 1000);
    });
}

function getOrders(userId) {
    return new Promise(resolve => {
        setTimeout(() => resolve(["Order1", "Order2"]), 1000);
    });
}

function getOrderDetails(order) {
    return new Promise(resolve => {
        setTimeout(() => resolve({ order, details: "Pizza, extra cheese" }), 1000);
    });
}

// Chaining Promises
getUser(1)
    .then(user => {
        console.log("User:", user);
        return getOrders(user.id);
    })
    .then(orders => {
        console.log("Orders:", orders);
        return getOrderDetails(orders[0]);
    })
    .then(details => {
        console.log("Order Details:", details);
    })
    .catch(error => console.log("Error:", error));

Output:

User: { id: 1, name: 'Alice' }
Orders: [ 'Order1', 'Order2' ]
Order Details: { order: 'Order1', details: 'Pizza, extra cheese' }

No more nested callbacks! Promises make it cleaner!

 

The Async/Await Magic

Async/Await makes your code look synchronous while still being asynchronous!

Using Async/Await

async function fetchOrderDetails() {
    try {
        let user = await getUser(1);
        console.log("User:", user);

        let orders = await getOrders(user.id);
        console.log("Orders:", orders);

        let details = await getOrderDetails(orders[0]);
        console.log("Order Details:", details);
    } catch (error) {
        console.log("Error:", error);
    }
}

fetchOrderDetails();

Output (Same as before, but cleaner!):

User: { id: 1, name: 'Alice' }
Orders: [ 'Order1', 'Order2' ]
Order Details: { order: 'Order1', details: 'Pizza, extra cheese' }

 Advantages of Async/Await:

  • Looks like synchronous code (easier to read).
  • No more .then() chaining.
  •  Try/Catch handles errors better.

 

Error Handling in Async/Await

Handling errors is simple with try/catch!

async function fetchPizza() {
    try {
        let result = await orderPizza("Hawaiian");
        console.log(result);
    } catch (error) {
        console.log("Oops! Error:", error);
    }
}

fetchPizza();

Output:

Ordering a Hawaiian pizza... 
Oops! Error: Sorry, we don’t serve pineapple on pizza! 

Always use try/catch to catch errors in async functions!

 

Conclusion

Now you're a master of Promises and Async/Await!

  • You understand why Promises are better than Callbacks.
  • You can chain Promises to avoid Callback Hell.
  • You can use Async/Await for cleaner, readable code.
  • You know how to handle errors with try/catch.

Happy coding!

 

 

Post a Comment

0 Comments