Skip to main content

Command Palette

Search for a command to run...

JavaScript Promises Explained for Beginners

Updated
5 min read

The Problem: Why Do We Need Promises?

Imagine you order a pizza.

You place the order and the pizza shop says:

"Your pizza will be delivered in 30 minutes."

Now you don't stand outside the shop waiting for 30 minutes. You continue doing other work and receive the pizza when it's ready.

Computers face a similar situation.

Sometimes JavaScript needs to wait for things like:

  • API requests

  • Database queries

  • Reading files

  • Timers

These tasks take time.

If JavaScript stopped everything and waited, the application would become slow and unresponsive.

So JavaScript starts the task and continues doing other work.

The question is:

How will JavaScript know when the result is ready?

That's where Promises come in.

What is a Promise?

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

Think of it as:

"I don't have the result right now, but I promise I'll give it to you later."

Example:

const promise = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve("Pizza Delivered!");
  }, 3000);
});

In this example:

  • Pizza is not available immediately.

  • After 3 seconds, it gets delivered.

  • The Promise notifies us when the result is ready.

Promise States

Every Promise can be in one of three states.

1. Pending

The task is still running.

Example:

const promise = new Promise(() => {});

Current state:

Pending

The result is not ready yet.

2. Fulfilled

The task completed successfully.

resolve("Success");

State:

Fulfilled

Result is available.

3. Rejected

Something went wrong.

reject("Error");

State:

Rejected

An error occurred.

Promise Lifecycle

A Promise always follows this path:

          Promise Created
                 |
                 v
             Pending
             /     \
            /       \
           v         v
      Fulfilled   Rejected

A Promise starts in the Pending state and eventually becomes either Fulfilled or Rejected.

It cannot go back to Pending.

Creating a Promise

Example:

const pizzaPromise = new Promise((resolve, reject) => {
  const pizzaReady = true;

  if (pizzaReady) {
    resolve("Pizza Delivered");
  } else {
    reject("Pizza Cancelled");
  }
});

Here:

  • resolve() means success.

  • reject() means failure.

Handling Success and Failure

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

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

Output:

Pizza Delivered

If something goes wrong:

Pizza Cancelled

Understanding .then()

.then() runs when the Promise is fulfilled.

promise.then((result) => {
  console.log(result);
});

Think of it as:

"When the task finishes successfully, run this code."

Understanding .catch()

.catch() runs when the Promise is rejected.

promise.catch((error) => {
  console.log(error);
});

Think of it as:

"If something fails, run this code."

Promises as Future Values

Let's imagine a friend says:

"I'll send you the notes tomorrow."

Right now you don't have the notes.

But you have a promise that you'll receive them later.

Similarly:

const notesPromise = getNotes();

At this moment:

notesPromise

does not contain the notes.

It contains a Promise that will provide the notes in the future.

Callback Approach

Before Promises, developers often used callbacks.

Example:

getUser(function(user) {
  getPosts(user.id, function(posts) {
    getComments(posts[0].id, function(comments) {
      console.log(comments);
    });
  });
});

Notice how code keeps moving to the right.

This is called:

Callback Hell

or

Pyramid of Doom

Because the code becomes difficult to read and maintain.

Promise Version

The same code can be written using Promises.

getUser()
  .then((user) => {
    return getPosts(user.id);
  })
  .then((posts) => {
    return getComments(posts[0].id);
  })
  .then((comments) => {
    console.log(comments);
  })
  .catch((error) => {
    console.log(error);
  });

This looks much cleaner and easier to understand.

Promise Chaining

A Promise can return another Promise.

This allows multiple asynchronous operations to run in sequence.

Example:

getUser()
  .then((user) => {
    return getPosts(user.id);
  })
  .then((posts) => {
    return getComments(posts[0].id);
  })
  .then((comments) => {
    console.log(comments);
  });

Each .then() waits for the previous Promise to finish.

This is called:

Promise Chaining

Real World Example

Fetching data from an API:

fetch("https://jsonplaceholder.typicode.com/users")
  .then((response) => response.json())
  .then((data) => {
    console.log(data);
  })
  .catch((error) => {
    console.log(error);
  });

Flow:

Send Request
      |
      v
   Pending
      |
      v
 Receive Response
      |
      v
 Fulfilled

If the network fails:

Rejected

Callback vs Promise

Callback Promise
Can lead to callback hell Cleaner code
Hard to manage errors Easy error handling
Difficult to read More readable
Nested structure Chaining structure

Conclusion

Promises help JavaScript handle tasks that take time, such as API calls, database operations, file reading, and timers.

Instead of deeply nested callbacks, Promises provide a cleaner and more readable way to manage asynchronous code.

Think of a Promise as a future value:

"The result is not available right now, but it will be available later."

Once you understand Promises, learning async/await becomes much easier because async/await is built on top of Promises.