Skip to content
elephantoo

Promises

Lesson 27 of 34 18 min read

Create and chain promises, handle errors, and combine them with all, allSettled, race and any.


A promise is an object representing a value that isn't available yet — the result of a network request, a timer or a file read. Instead of passing a callback into a function, the function returns a promise, and you attach handlers to it. Promises fix the problems of callback hell: they chain, they have one consistent way to handle errors, and they can be combined.

The life of a promise#

A promise is always in one of three states:

  • pending — the work is in progress
  • fulfilled — it finished successfully, with a value
  • rejected — it failed, with a reason (usually an Error)

Once fulfilled or rejected, a promise is settled and never changes again.

Consuming promises: then, catch, finally#

Most of the time you use promises that APIs give you. fetch, for example, returns a promise:

JavaScript
fetch("https://jsonplaceholder.typicode.com/users/1")
  .then((response) => response.json()) // also returns a promise
  .then((user) => console.log(user.name))
  .catch((err) => console.error("Request failed:", err.message))
  .finally(() => console.log("Done"));
Output
Leanne Graham
Done
  • .then(onFulfilled) runs when the promise fulfils, receiving its value.
  • .catch(onRejected) runs if anything earlier in the chain rejected or threw.
  • .finally(fn) runs either way — ideal for hiding a loading spinner.

Every one of these returns a new promise, which is what makes chaining possible.

Creating promises#

Wrap a callback-based or timer-based operation with new Promise:

JavaScript
function wait(ms) {
  return new Promise((resolve) => setTimeout(resolve, ms));
}

function rollDice() {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      const roll = Math.floor(Math.random() * 6) + 1;
      if (roll === 1) {
        reject(new Error("Rolled a 1 – you lose!"));
      } else {
        resolve(roll);
      }
    }, 200);
  });
}

wait(100).then(() => console.log("100 ms passed"));

rollDice()
  .then((n) => console.log(`You rolled ${n}`))
  .catch((err) => console.log(err.message));

The function you pass to new Promise is the executor. It runs immediately and receives two functions: call resolve(value) on success or reject(error) on failure. Only the first call counts.

Shortcuts for already-settled promises:

JavaScript
Promise.resolve(42).then((v) => console.log(v));            // 42
Promise.reject(new Error("nope")).catch((e) => console.log(e.message)); // nope

Chaining#

Whatever a .then callback returns becomes the value for the next .then. If it returns a promise, the chain waits for it:

JavaScript
const wait = (ms, value) => new Promise((resolve) => setTimeout(() => resolve(value), ms));

const getUser = (id) => wait(100, { id, name: "Ada" });
const getOrders = (user) => wait(100, [{ id: 501, total: 1299 }, { id: 502, total: 349 }]);

getUser(1)
  .then((user) => {
    console.log("User:", user.name);
    return getOrders(user); // return the promise!
  })
  .then((orders) => orders.reduce((sum, o) => sum + o.total, 0))
  .then((total) => console.log("Total spent: ₹" + total));
Output
User: Ada
Total spent: ₹1648

Compare this flat chain with the nested pyramid from the callbacks lesson.

The #1 promise bug is forgetting to return inside .then. Without it, the next step runs immediately with undefined, and errors from the inner promise aren't caught by your .catch.

Error handling#

A rejection — or an error thrown inside any handler — skips the following .thens and jumps to the next .catch:

JavaScript
Promise.resolve(10)
  .then((n) => {
    if (n > 5) throw new Error("Too big!");
    return n;
  })
  .then((n) => console.log("never runs", n))
  .catch((err) => {
    console.log("Caught:", err.message);
    return 5; // recover with a fallback value
  })
  .then((n) => console.log("Continuing with", n));
Output
Caught: Too big!
Continuing with 5

A .catch can recover (by returning a value) or re-throw to pass the error on. Put one .catch at the end of each chain.

Unhandled rejections

If a promise rejects and nobody catches it, you get an "unhandled promise rejection" warning in the browser console. In Node.js, the process crashes by default. Always end chains with .catch (or use try/catch with await).

Combining promises#

Promise.all — run in parallel, wait for all

JavaScript
const wait = (ms, value) => new Promise((resolve) => setTimeout(() => resolve(value), ms));

console.time("all");
Promise.all([wait(300, "user"), wait(200, "orders"), wait(100, "settings")]).then((results) => {
  console.log(results); // [ 'user', 'orders', 'settings' ] – in input order
  console.timeEnd("all"); // ~300 ms, not 600 ms
});

Results keep the input order, regardless of which finished first. If any promise rejects, Promise.all rejects immediately with that error.

Promise.allSettled — wait for all, never rejects

JavaScript
const tasks = [
  Promise.resolve("profile loaded"),
  Promise.reject(new Error("recommendations failed")),
  Promise.resolve("courses loaded"),
];

Promise.allSettled(tasks).then((results) => {
  for (const r of results) {
    console.log(r.status === "fulfilled" ? `✅ ${r.value}` : `❌ ${r.reason.message}`);
  }
});
Output
✅ profile loaded
❌ recommendations failed
✅ courses loaded

Use it when partial success is fine — e.g. loading independent widgets on a dashboard.

Promise.race — first to settle wins

JavaScript
function timeout(ms) {
  return new Promise((_, reject) => setTimeout(() => reject(new Error(`Timed out after ${ms} ms`)), ms));
}

const slowTask = new Promise((resolve) => setTimeout(() => resolve("finished"), 1000));

Promise.race([slowTask, timeout(300)])
  .then(console.log)
  .catch((err) => console.log(err.message)); // Timed out after 300 ms

(For fetch, there's a built-in way to time out with AbortSignal.timeout — see fetch, APIs & JSON.)

Promise.any — first to fulfil wins

JavaScript
const mirrors = [
  Promise.reject(new Error("mirror 1 down")),
  new Promise((resolve) => setTimeout(() => resolve("mirror 2"), 200)),
  new Promise((resolve) => setTimeout(() => resolve("mirror 3"), 100)),
];

Promise.any(mirrors).then((fastest) => console.log("Using", fastest)); // Using mirror 3

It rejects only if all fail, with an AggregateError containing every reason.

MethodResolves whenRejects when
Promise.allall fulfil (array of values)any rejects
Promise.allSettledall settle (array of outcomes)never
Promise.racethe first settles (fulfils)the first settles (rejects)
Promise.anythe first fulfilsall reject (AggregateError)

Promise.withResolvers#

Sometimes you need to resolve a promise from outside the executor — e.g. waiting for a user to click a button in a dialog:

JavaScript
function waitForClick(button) {
  const { promise, resolve } = Promise.withResolvers();
  button.addEventListener("click", resolve, { once: true });
  return promise;
}

// await waitForClick(document.querySelector("#ok"));

Promise callbacks are always asynchronous#

Even if a promise is already resolved, .then callbacks run later, as microtasks (see The event loop):

JavaScript
console.log("1");
Promise.resolve().then(() => console.log("3"));
console.log("2");
Output
1
2
3

This consistency avoids the "sometimes sync, sometimes async" bugs of callback APIs.

Common mistakes#

  • Forgetting to return a promise inside .then.
  • Nesting .thens inside .thens (recreating callback hell) — return and chain instead.
  • Missing a final .catch, causing unhandled rejections.
  • Wrapping a function that already returns a promise in new Promise (the "explicit construction anti-pattern"). Just return the existing promise.
  • Running independent requests one after another instead of in parallel with Promise.all.

What's next#

Promise chains are much better than callbacks, but there's an even more readable syntax built on top of them: async and await.

Check your understanding

Quick quiz

0/3 answered
  1. 1.What are the three states of a promise?

  2. 2.What does Promise.all([p1, p2, p3]) do if p2 rejects?

  3. 3.Inside a .then callback, what happens if you return another promise?

Finished reading?

Mark this lesson complete to track your progress.