DevAcademy
AdvancedJavaScript

Promises

Learn how JavaScript Promises represent the eventual result of an asynchronous operation, and how to use .then(), .catch(), Promise.all(), Promise.race(), and Promise.allSettled().

Reading Time

20 min

Lesson

Lesson 30 of 48

What Is a Promise?

A Promise is an object representing the eventual result of an asynchronous operation — something that hasn't finished yet, but will finish (successfully or not) at some point in the future. Instead of forcing your code to freeze and wait, a Promise lets you keep going and attach instructions for what to do once the result is ready. This solves the exact problem from the previous lesson: it gives asynchronous work a proper, catchable way to report success or failure.

The Three States of a Promise

StateMeaning
PendingThe initial state — the operation has not completed yet
FulfilledThe operation completed successfully, and has a resulting value
RejectedThe operation failed, and has a reason (typically an Error)

Creating a Promise

You create a Promise with the Promise constructor, which takes a single function called the executor. The executor receives two functions as arguments — resolve and reject — call resolve(value) when the operation succeeds, or reject(error) when it fails. Once a promise settles (resolves or rejects), its state is locked in permanently; it can never change again.

The Promise Constructor

Try it yourself — edit and run

Console Output

Click “Run” to see the console output here.

Consuming a Promise: then, catch, finally

.then() registers callbacks to run when a promise fulfills (first argument) or rejects (second, optional argument). .catch() is shorthand for handling only the rejection case, and reads more cleanly when placed after one or more .then() calls. .finally() runs once the promise settles, regardless of whether it fulfilled or rejected — just like the finally block from try/catch, it's the place for cleanup that must always happen.

then / catch / finally

Try it yourself — edit and run

Console Output

Click “Run” to see the console output here.

Chaining Promises

Each call to .then() returns a brand new promise, which is what makes chaining possible. If you return a plain value from inside a .then() callback, that value becomes the fulfillment value of the next promise in the chain. If you return another promise, the chain waits for that promise to settle before continuing — this is how you sequence multiple asynchronous steps, one after another, without nesting callbacks inside callbacks.

Chaining Multiple Steps

Try it yourself — edit and run

Console Output

Click “Run” to see the console output here.

Forgetting to Return Inside a .then()

A very common bug: forgetting to return the promise (or value) you're producing inside a .then() callback. Without the return, the next .then() in the chain runs immediately with undefined instead of waiting for your inner operation to finish — the chain silently stops tracking the work you actually care about.

The Missing return Bug

Try it yourself — edit and run

Console Output

Click “Run” to see the console output here.

Promise.all() — Run Many, Wait for All

Promise.all() takes an array of promises and returns a single promise that fulfills with an array of all their results, in the same order, once every one of them has fulfilled. If even one of the promises rejects, Promise.all() immediately rejects with that error, without waiting for the others. It's the right tool when you need several independent operations to all succeed before moving on.

Promise.all() Example

Try it yourself — edit and run

Console Output

Click “Run” to see the console output here.

Promise.race() — First One Wins

Promise.race() also takes an array of promises, but settles as soon as the first one settles — whether that first one fulfills or rejects. This is useful for scenarios like timeouts, where you want to race a real operation against a promise that rejects after a set duration, so a slow request doesn't hang forever.

Promise.race() Example

Try it yourself — edit and run

Console Output

Click “Run” to see the console output here.

Promise.allSettled() — Wait for Everything, No Matter What

Unlike Promise.all(), Promise.allSettled() never short-circuits on a rejection. It waits for every promise to settle and returns an array of result objects, each shaped like { status: 'fulfilled', value } or { status: 'rejected', reason }. Reach for this when you want to know the outcome of every operation — successes and failures alike — instead of stopping at the first failure.

Promise.allSettled() Example

Try it yourself — edit and run

Console Output

Click “Run” to see the console output here.

Choosing the Right Combinator

Use Promise.all() when every operation must succeed and you'll bail out if any fail. Use Promise.allSettled() when you want the outcome of every operation regardless of individual failures. Use Promise.race() when you only care about whichever promise settles first, such as implementing a timeout.

Interview Questions

Quick Quiz

1. Which state indicates a Promise has not yet completed?

2. What do the two arguments passed to the Promise constructor executor do?

3. What is the main risk of forgetting to return a promise inside a .then() callback?

4. How does Promise.all() differ from Promise.allSettled()?

5. Which combinator is best suited for implementing a request timeout?

6. What does this code log?

Promise.resolve(1)
  .then((value) => {
    console.log(value);
    return value + 1;
  })
  .then((value) => {
    console.log(value);
  });

7. What does this code log?

Promise.reject("Error!")
  .then(() => console.log("resolved"))
  .catch((err) => console.log("caught:", err));