Skip to main content

Command Palette

Search for a command to run...

JavaScript Promises Explained for Beginners

Updated
•3 min read•View as Markdown

Introduction

In the previous blog, we learned about callbacks and how they help handle asynchronous tasks.

However, callbacks come with a major problem:

Callback Hell — deeply nested and hard-to-read code.

To solve this issue, JavaScript introduced:

Promises

Promises make asynchronous code more readable, structured, and easier to manage.


What Problem Do Promises Solve?

Let’s look at a callback-based example:

setTimeout(() => {
  console.log("Step 1");

  setTimeout(() => {
    console.log("Step 2");

    setTimeout(() => {
      console.log("Step 3");
    }, 1000);

  }, 1000);

}, 1000);

Problems:

  • Nested structure

  • Hard to read

  • Difficult to debug

  • Poor maintainability

This is called callback hell.


What is a Promise?

A Promise is:

An object that represents the future result of an asynchronous operation.

Think of it like this:

  • “I promise I will give you the result later.”

Promise States

A Promise has three states:

  1. Pending

    • Initial state

    • Task is still running

  2. Fulfilled

    • Operation completed successfully

    • Result is available

  3. Rejected

    • Operation failed

    • Error is available


Basic Promise Example

Creating a Promise:

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

  if (success) {
    resolve("Task completed successfully");
  } else {
    reject("Task failed");
  }
});

Handling Success and Failure

We use .then() and .catch():

myPromise
  .then((result) => {
    console.log(result);
  })
  .catch((error) => {
    console.log(error);
  });

Explanation:

  • .then() runs when the promise is fulfilled

  • .catch() runs when the promise is rejected


Real-Life Example

Imagine you ordered a product online:

  • Order placed → Pending

  • Delivered → Fulfilled

  • Cancelled → Rejected


Promise Lifecycle (Concept)

Pending → Fulfilled (resolve)
        → Rejected (reject)

Promise Chaining

One of the biggest advantages of promises is chaining.

Example:

function step1() {
  return new Promise((resolve) => {
    setTimeout(() => resolve("Step 1 done"), 1000);
  });
}

function step2() {
  return new Promise((resolve) => {
    setTimeout(() => resolve("Step 2 done"), 1000);
  });
}

function step3() {
  return new Promise((resolve) => {
    setTimeout(() => resolve("Step 3 done"), 1000);
  });
}

step1()
  .then((res) => {
    console.log(res);
    return step2();
  })
  .then((res) => {
    console.log(res);
    return step3();
  })
  .then((res) => {
    console.log(res);
  })
  .catch((err) => {
    console.log(err);
  });

Benefits:

  • Flat structure (no nesting)

  • Easy to read

  • Better error handling


Callback vs Promise

Callback Style:

doTask(function(result) {
  doNext(result, function(nextResult) {
    doAnother(nextResult, function(finalResult) {
      console.log(finalResult);
    });
  });
});

Promise Style:

doTask()
  .then(doNext)
  .then(doAnother)
  .then(console.log)
  .catch(console.error);

Key Difference:

  • Callbacks → nested

  • Promises → chained


Common Mistakes

  • Not returning promises in .then()

  • Forgetting .catch()

  • Mixing callbacks and promises unnecessarily


Interview Points

  • Promises solve callback hell

  • They represent future values

  • Three states: pending, fulfilled, rejected

  • .then() and .catch() are used

  • Support chaining


Conclusion

Promises are a powerful improvement over callbacks:

  • Cleaner code

  • Better readability

  • Easier error handling

They are the foundation for modern asynchronous JavaScript.