Node.js: Code Outside the Browser / Async Programming in Node.js

Callback, Promise and Async Await

JavaScript’s asynchronous behavior is at the heart of modern web applications, especially when dealing with API calls, timers, or user interactions. Over time, three major patterns have evolved to handle asynchronous workflows: Callbacks, Promises, and Async/Await. While all three achieve the same goal, their syntax and developer experience vary significantly. This simplified comparison uses one consistent example to clearly show how each method works and why newer approaches offer cleaner and more maintainable code. Lets understand it with below example:

Ordering Food Online (Same steps for all 3 examples)

1️⃣ Order accepted

2️⃣ Food prepared

3️⃣ Food delivered

We will use one set of API-like functions for all examples.


🍽️ Shared Functions (Used Everywhere)

These simulate dummy API calls using setTimeout.

function orderFoodAPI() {
  return new Promise((resolve) => {
    setTimeout(() => resolve("Restaurant accepted order"), 1000);
  });
}

function prepareFoodAPI() {
  return new Promise((resolve) => {
    setTimeout(() => resolve("Food prepared"), 1500);
  });
}

function deliverFoodAPI() {
  return new Promise((resolve) => {
    setTimeout(() => resolve("Food delivered"), 2000);
  });
}

✔ Now we won’t repeat these again.


1️⃣ CALLBACK VERSION

⭐ Concept: Pass a function that runs after task finishes

function orderFood(callback) {
  orderFoodAPI().then(result => {
    console.log(result);
    callback();
  });
}

function prepareFood(callback) {
  prepareFoodAPI().then(result => {
    console.log(result);
    callback();
  });
}

function deliverFood(callback) {
  deliverFoodAPI().then(result => {
    console.log(result);
    callback();
  });
}

orderFood(() => {
  prepareFood(() => {
    deliverFood(() => {
      console.log("Enjoy your meal! 🍕");
    });
  });
});

❌ Callback Hell

Nested, messy, hard to maintain.


2️⃣ PROMISE VERSION

⭐ Concept: “When this is done, then do this”

orderFoodAPI()
  .then(result => {
    console.log(result);
    return prepareFoodAPI();
  })
  .then(result => {
    console.log(result);
    return deliverFoodAPI();
  })
  .then(result => {
    console.log(result);
    console.log("Enjoy your meal! 🍕");
  })
  .catch(() => console.log("Order failed ❌"));

✔ Cleaner than callbacks

But .then().then().then() grows long.


3️⃣ ASYNC / AWAIT VERSION

⭐ Concept: Write async code like synchronous steps

async function startOrder() {
  try {
    console.log(await orderFoodAPI());
    console.log(await prepareFoodAPI());
    console.log(await deliverFoodAPI());
    console.log("Enjoy your meal! 🍕");
  } catch {
    console.log("Order failed ❌");
  }
}

startOrder();

⭐ Cleanest because

Looks like simple step-by-step instructions.



🏆 FINAL COMPARISON TABLE

Feature

Callback

Promise

Async/Await

Readability

❌ Worst

✔ Good

⭐ Best

Structure

Nested

Chained

Straight

Error Handling

Poor

Good

Best

Modern Usage

Low

Medium

⭐ High

Best For

Small tasks

Medium workflows

Real-world async code