Skip to main content

Command Palette

Search for a command to run...

Callbacks vs Promises Explained So Simply You’ll Never Forget

Updated
Callbacks vs Promises Explained So Simply You’ll Never Forget
A
I’m a Backend Developer currently learning and growing into Full-Stack Web Development. My main focus is building clean, reliable backend APIs using Node.js, Express, MongoDB, and Redis, while gradually improving my frontend and full-stack skills. On this blog, I share simple, practical explanations of what I’m learning in backend concepts, real development problems, and full-stack topics written in a way that’s easy to understand for other learners. 🎯 Open to Backend Developer / Software Engineer roles 📩 Contact: ashishjha1304@gmail.com

Most developers struggle with async JavaScript.

Here’s what you’ll finally understand:

Introduction

When we write code, we usually expect it to run line by line from top to bottom. But in real applications, some tasks take time, like reading a file, calling an API, or talking to a database.

If Node.js waited for each task to finish, the whole app would become slow and stuck. That is why Node.js uses async (asynchronous) code, so it can handle multiple things at the same time without blocking.

In this blog, I will explain how async code works in Node.js using callbacks and promises, in a very simple way.


Why Async Code Exists in Node.js

Node.js can handle many users at the same time. For example, a server may receive thousands of requests.

If one request takes time (like reading a file), Node.js does not want to stop everything else. Instead, it says:

👉 “Start this task, and when it is done, tell me. I will do other work meanwhile.”

This is async behavior.

💡 Simple Example:
Think of ordering food in a restaurant.
You place an order and instead of standing at the counter, you sit and wait.
When food is ready, they call your name.

Node.js works in a similar way.


Example Scenario: Reading a File

Let’s say we want to read a file using Node.js.

This is a perfect example because file reading takes time, and we don’t want to block the system while waiting.


Callback-Based Async Execution

A callback is simply a function that we pass as an argument. This function will be called later when the task is done.

Code Example (Callback)

const fs = require("fs");

fs.readFile("data.txt", "utf-8", (err, data) => {
  if (err) {
    console.log("Error reading file");
    return;
  }

  console.log("File content:", data);
});

Explanation (Step-by-Step)

  1. We call fs.readFile to read a file.

  2. Node.js starts reading the file in the background.

  3. It does NOT wait here. It moves to other work.

  4. When the file is ready, it runs the callback function.

  5. Inside the callback, we get either:

    • err → if something went wrong

    • data → file content if successful

Insight:
Callback tells Node.js:
“When done, run this function.”


How Callback Flow Works

Imagine this flow:

Start reading file
      ↓
Do other work
      ↓
File finished reading
      ↓
Run callback function

This is how Node.js stays fast and non-blocking.


Problems with Nested Callbacks (Callback Hell)

Callbacks work fine for simple tasks. But when tasks depend on each other, things get messy.

Example (Nested Callbacks)

fs.readFile("file1.txt", "utf-8", (err, data1) => {
  if (err) return;

  fs.readFile("file2.txt", "utf-8", (err, data2) => {
    if (err) return;

    fs.readFile("file3.txt", "utf-8", (err, data3) => {
      if (err) return;

      console.log(data1, data2, data3);
    });
  });
});

What’s the Problem?

  • Code goes deeper and deeper (hard to read)

  • Error handling becomes confusing

  • Debugging becomes difficult

Insight:
This messy structure is called Callback Hell.


Promise-Based Async Handling

To solve callback problems, JavaScript introduced Promises.

A Promise represents a value that will be available in the future.

It has 3 states:

  • Pending → still working

  • Resolved → success

  • Rejected → error


Code Example (Promise)

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

fs.readFile("data.txt", "utf-8")
  .then((data) => {
    console.log("File content:", data);
  })
  .catch((err) => {
    console.log("Error reading file");
  });

Explanation (Step-by-Step)

  1. We call readFile, which returns a Promise.

  2. .then() runs when the task is successful.

  3. .catch() runs if there is an error.

  4. No nesting is needed.

Insight:
Promises separate success and error logic clearly.


Promise Flow (Simple Idea)

Start task
   ↓
Pending
   ↓
Success → .then()
   ↓
OR
   ↓
Error → .catch()

Callback vs Promise (Readability Comparison)

Callback Style:

  • Hard to read when nested

  • Error handling is messy

  • Code becomes deep

Promise Style:

  • Cleaner and flat structure

  • Easy to chain multiple tasks

  • Better error handling

Simple Thought:
Callbacks = “Call me later”
Promises = “I will give you result later”


Benefits of Promises

  • Code is easier to read

  • Avoids callback hell

  • Better error handling

  • Easy to chain multiple async tasks

Real Use Case:

In backend apps, you often:

  • Fetch data

  • Process it

  • Save it

Promises make this flow much cleaner.


Summary

Async code is very important in Node.js because it keeps the system fast and non-blocking.

Callbacks were the first way to handle async tasks, but they become messy when used too much.

Promises solve this problem by making code cleaner, easier to read, and better structured.

If you understand this well, you are one step closer to writing strong backend code.

JavaScript Made Simple

Part 1 of 2

A beginner-to-advanced JavaScript series that breaks down complex concepts into simple, visual, and practical explanations. No fluff. No confusion. You’ll learn how JavaScript actually works behind the scenes — with real examples, mental models, and interview-focused insights. Perfect for: • Beginners who feel lost in JS • Developers preparing for interviews • Anyone tired of shallow tutorials By the end, JavaScript will finally *make sense*.

Up next

Stop Memorizing Array Methods — Understand Them

Most developers use array methods like map, filter, and reduce… …but don’t actually understand how they work. Let’s fix that — simply. 1. Introduction When we work with JavaScript, we use arrays almo

More from this blog

A

Ashish Jha · Dev

34 posts

A personal blog where I write simple and practical explanations about backend development, Git, JavaScript, and full-stack web development as I learn and build.