Skip to main content

Command Palette

Search for a command to run...

Javascript Promises Explained for beginners

Updated
3 min read
Javascript Promises Explained for beginners

Hello readers,

Welcome back to another blog. In the last blog, we talked about callbacks - why they exist and the problems they bring (callback hell, pyramid of doom, dependency chaos).

If you haven't read that yet, read here:

https://js-with-abhishek.hashnode.dev/callbacks

Now think about this

  • What if we could handle async code in a cleaner way?

  • What if we could avoid deeply nested callbacks?

  • What if our code looked more like normal step-by-step logic?

That’s where Promises come in.

What Problem Do Promises Solve?

Let’s recall the classic callback problem:

orderFood(function(order) {
  makePayment(order, function(payment) {
    deliverFood(payment, function(delivery) {
      console.log("Delivered:", delivery);
    });
  });
});

This creates:

  • Nested structure (callback hell)

  • Hard to read

  • Hard to debug

  • Error handling becomes messy

Promises solve this by giving us:

  • Better readability

  • Linear flow of async code

  • Centralized error handling

What is a Promise?

A Promise is basically a future value.

It represents:

“I don’t have the result right now… but I will give it to you later.”

Example:

const promise = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve("Food delivered ");
  }, 2000);
});

Here:

  • resolve → success

  • reject → failure

We will understand this code snippet ahead.

Promise States

Every promise has 3 states:

  1. Pending

    1. Initial state.

    2. Work is still going on

  2. Fulfilled

    1. Operation completed successfully,

    2. resolve() is called

  3. Rejected

    1. Operation failed

    2. reject() is called

Think of it like ordering food:

  • Pending → Order placed

  • Fulfilled → Food delivered

  • Rejected → Order canceled

Basic Promise Lifecycle

const orderFood = new Promise((resolve, reject) => {
  let success = true;

  if (success) {
    resolve("Order successful");
  } else {
    reject("Order failed");
  }
});

Now consuming it:

const orderFood = new Promise((resolve, reject) => {
  let success = true;

  if (success) {
    resolve("Order successful");
  } else {
    reject("Order failed");
  }
});

Flow:

  1. Promise created

  2. Async work happens

  3. Either:

    • resolve().then() runs

    • reject().catch() runs

Handling Success and Failure

Success → .then()

promise.then((data) => {
  console.log("Success:", data);
});

Failure → .catch()

promise.catch((error) => {
  console.log("Error:", error);
});

Always Run → .finally()

promise.finally(() => {
  console.log("Done (success or failure)");
});

Promise Chaining (Most Important)

This is where promises shine.

Instead of nesting, we chain:

orderFood()
  .then((order) => makePayment(order))
  .then((payment) => deliverFood(payment))
  .then((delivery) => {
    console.log("Delivered:", delivery);
  })
  .catch((error) => {
    console.log("Something went wrong:", error);
  });

What changed?

  • No nesting

  • Clean flow

  • Looks synchronous (but isn’t!)

So yeah… that’s Promises in a nutshell

We started with messy callbacks…
Moved to cleaner, structured async handling…
And saw how promises make our code readable, predictable, and scalable.

But wait…
Promises themselves come with some powerful built-in methods that can take things to the next level — especially when dealing with multiple async operations.

Things like:

  • Running multiple promises together

  • Handling the fastest or all results

  • Managing failures smartly

I’ve already covered all of that in detail in my dedicated blog on Promise Methods

🔗 Read it here:
https://js-with-abhishek.hashnode.dev/


And in the next blog… We’ll take one more step forward:
async/await — making async code look like sync magic

Stay consistent and keep grinding.

Peace ✌️