Skip to main content

Command Palette

Search for a command to run...

JavaScript Promises Explained for Beginners

Escaping Callback Hell

Updated
6 min read
JavaScript Promises Explained for Beginners

What problem promises solve

JavaScript Promises Explained for Beginners: Escaping Callback Hell

In our last article, we learned that asynchronous JavaScript relies on Callbacks passing a function into another function to run when a background task finishes.

But we also discovered a massive flaw: when we have multiple tasks that depend on each other, callbacks get nested inside of callbacks, creating the dreaded Callback Hell (or Pyramid of Doom). The code becomes a chaotic, unreadable triangle.

To fix this, modern JavaScript introduced Promises. Promises completely revolutionized how we write asynchronous code, changing it from a nested nightmare into a clean, flat, and highly readable sequence.

Let’s break down exactly what a Promise is and how to use it.

What is a Promise? (The "Future Value")

Imagine you go to a busy fast-food restaurant and order a burger. You pay, and the cashier hands you a receipt with an order number (let's say, #42).

You don't have your burger yet, but that receipt is a promise that you will get your burger in the future. You can sit down, read a book, and wait. Eventually, the kitchen will call #42. They will either hand you a fresh burger (Success!), or they will tell you they ran out of buns (Failure!).

In JavaScript, a Promise is exactly like that receipt. It is an object that represents a future value the eventual completion (or failure) of an asynchronous operation.

Promise states (pending, fulfilled, rejected)

Just like your order at the restaurant, a JavaScript Promise exists in one of three specific states:

  1. Pending: The initial state. You have your receipt, but the kitchen is still cooking. The promise is neither fulfilled nor rejected yet.

  2. Fulfilled (Resolved): The operation completed successfully. Your burger is ready!

  3. Rejected: The operation failed. They ran out of buns, or the network connection dropped.

Note: Once a promise is fulfilled or rejected, it is considered Settled. A settled promise can never change its state again.

Basic promise lifecycle

  • Initialization: A promise is created using the new Promise() constructor. It immediately starts in the Pending state.

  • Execution: The constructor accepts an "executor" function that runs automatically. This function performs the asynchronous task (like a data fetch or a timer) and receives two internal callbacks: resolve and reject.

  • Settling (Transitioning)
    1. Success: Calling resolve(value) transitions the state to Fulfilled.
    2. Failure: Calling reject(error) transitions the state to Rejected.

  • 4.Reaction (Consumption) Once settled, the promise triggers handlers registered by the consumer:
    1. .then(): Executes if the promise is fulfilled.
    2. .catch(): Executes if the promise is rejected.
    3. .finally(): Executes regardless of the outcome to perform cleanup.

Handling success and failure

into the function anymore. Instead, the function returns the Promise object, and you attach listeners to it using .then() and .catch().

  • .then() runs only if the promise is Fulfilled.

  • .catch() runs only if the promise is Rejected

Promise chaining concept

Remember the Pyramid of Doom? If we wanted to get a user, then get their orders, then get their payment, we had to nest callbacks deeply.

The greatest superpower of Promises is Chaining. Every time you return a value inside a .then(), it automatically wraps that value in a new Promise. This means you can chain multiple .then() blocks together in a flat, readable line.

If any step in the chain fails, it skips the rest of the .then() blocks and falls straight down into a single .catch() at the bottom.

Callbacks vs. Promises Comparison

The Code Difference:

// THE OLD CALLBACK WAY (Ugly & Nested)
getUser(101, (user) => {
  getOrders(user.id, (orders) => {
    getPayment(orders[0].id, (payment) => {
      console.log(payment);
    });
  });
});

// THE NEW PROMISE WAY (Clean & Flat)
getUser(101)
  .then((user) => getOrders(user.id))
  .then((orders) => getPayment(orders[0].id))
  .then((payment) => console.log(payment))
  .catch((error) => console.log("Something failed in the chain:", error));

Practical Assignment: Creating and Consuming a Promise

For my web development cohort evaluation, here is a practical script where I actually create a custom promise from scratch to simulate a server request and then consume it using .then and .catch.

// Creating a Custom Promise
// We wrap an async task (setTimeout) inside a Promise
function orderBurger() {
  console.log("1. Cashier: Here is your receipt. Please wait.");

  return new Promise((resolve, reject) => {
    
    // Simulating cooking time (2 seconds)
    setTimeout(() => {
      // Let's randomly decide if the kitchen succeeds or fails
      const kitchenHasBuns = Math.random() > 0.3; // 70% success rate

      if (kitchenHasBuns) {
        resolve("Double Cheeseburger"); // Changes state to Fulfilled
      } else {
        reject("Sorry, we are out of buns!"); // Changes state to Rejected
      }
    }, 2000);
    
  });
}

// Consuming the Promise (The readable part!)

console.log("--- Welcome to JS Burger ---");

orderBurger()
  .then((food) => {
    // This only runs if resolve() is called
    console.log("2. Customer: YAY! I got my " + food);
  })
  .catch((errorMsg) => {
    // This only runs if reject() is called
    console.log("2. Customer: OH NO! " + errorMsg);
  })
  .finally(() => {
    // .finally() runs regardless of success or failure!
    console.log("3. Customer: Leaves the restaurant.");
  });

/* Note: Run this a few times! 
   70% of the time you get a burger. 
   30% of the time you get an error! */

E:\Hitesh_Web_Dev_2026\blog_folder\19.JS-JavaScript Promises Explained for Beginners>node 19assign.js
--- Welcome to JS Burger ---
1. Cashier: Here is your receipt. Please wait.
2. Customer: YAY! I got my Double Cheeseburger
3. Customer: Leaves the restaurant.

E:\Hitesh_Web_Dev_2026\blog_folder\19.JS-JavaScript Promises Explained for Beginners>node 19assign.js
--- Welcome to JS Burger ---
1. Cashier: Here is your receipt. Please wait.
2. Customer: OH NO! Sorry, we are out of buns!
3. Customer: Leaves the restaurant.

Conclusion

Promises saved JavaScript developers from the nightmare of Callback Hell. By representing asynchronous tasks as a "future value" that is either Fulfilled or Rejected, our code reads much more like a natural, top-to-bottom story.

Instead of nesting functions deep into a triangle, we can confidently link tasks together using .then() and catch any errors safely at the end with .catch().

Real-Life Analogy

Relationship:

  • Pending → talking stage

  • Fulfilled → shaadi

  • Rejected → breakup

Promise bhi same hi karta hai

Quick Summary

  • Promise = future value

  • 3 states → pending, fulfilled, rejected

  • .then() → success

  • .catch() → failure

  • Chaining → step-by-step execution

  • Callback ka upgraded version

Memory Trick

Promise = “Future ka commitment”

JavaScript

Part 7 of 25

Learning JavaScript in Web Dev Cohort 2026 by Team ChaiCode

Up next

Callbacks in JavaScript: Why They Exist

What They Are & Why We Use Them