Promises

A promise is an object representing a value that isn't available yet — the eventual result of an asynchronous operation. Instead of passing callbacks in, an async function hands back a promise, and you attach what should happen next with .then(), .catch() and .finally().

Promise states

A promise is always in one of three states:

  • pending — the operation hasn't finished yet;
  • fulfilled — it succeeded, with a value;
  • rejected — it failed, with a reason (usually an Error).

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

Creating a promise

Most of the time you'll use promises returned by APIs like fetch. To wrap a callback-based or timer-based operation yourself, use the Promise constructor:

JS create.js
function wait(ms) {
  return new Promise(resolve => setTimeout(resolve, ms));
}

function rollDie() {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      const value = 4;
      if (value < 1) reject(new Error("Bad roll"));
      else resolve(value);
    }, 20);
  });
}

wait(10).then(() => console.log("waited"));
rollDie().then(n => console.log("rolled", n));
Output
waited
rolled 4

The function passed to new Promise (the executor) runs immediately and receives two functions: call resolve(value) on success or reject(error) on failure.

then, catch, finally

JS handlers.js
function divide(a, b) {
  return new Promise((resolve, reject) => {
    if (b === 0) reject(new Error("Division by zero"));
    else resolve(a / b);
  });
}

divide(10, 4)
  .then(result => console.log("result:", result))
  .catch(err => console.log("error:", err.message))
  .finally(() => console.log("first done"));

divide(1, 0)
  .then(result => console.log("result:", result))
  .catch(err => console.log("error:", err.message));
Output
result: 2.5
error: Division by zero
first done

then handles success, catch handles failure, and finally runs either way — ideal for hiding a loading spinner.

Chaining

Every .then() returns a new promise, resolved with whatever its callback returns. If the callback returns another promise, the chain waits for it. This turns nested callbacks into a flat sequence:

JS chain.js
const wait = (ms, value) => new Promise(r => setTimeout(() => r(value), ms));

wait(10, 2)
  .then(n => n * 10)
  .then(n => wait(10, n + 1))
  .then(n => {
    console.log("final:", n);
    throw new Error("something broke");
  })
  .then(() => console.log("skipped"))
  .catch(err => console.log("caught:", err.message))
  .then(() => console.log("chain continues after catch"));
Output
final: 21
caught: something broke
chain continues after catch

An error anywhere — a rejection or a thrown exception — skips the following thens and jumps to the next catch. A single catch at the end handles errors from every step.

Common mistake: forgetting to return inside a then callback. Without it, the next step runs immediately with undefined instead of waiting for the inner promise.

Running promises together

JS combinators.js
const wait = (ms, value, fail) =>
  new Promise((res, rej) => setTimeout(() => (fail ? rej(new Error(value)) : res(value)), ms));

Promise.all([wait(30, "a"), wait(10, "b"), wait(20, "c")])
  .then(values => console.log("all:", values));

Promise.allSettled([wait(10, "ok"), wait(20, "nope", true)])
  .then(results => console.log("allSettled:", results.map(r => r.status)));

Promise.race([wait(30, "slow"), wait(10, "fast")])
  .then(v => console.log("race:", v));

Promise.any([wait(10, "x", true), wait(20, "y")])
  .then(v => console.log("any:", v));
Output
race: fast
allSettled: [ 'fulfilled', 'rejected' ]
any: y
all: [ 'a', 'b', 'c' ]
MethodFulfils when…Rejects when…
Promise.allAll fulfil (values in original order)Any one rejects
Promise.allSettledAll settle — never rejects—
Promise.raceThe first to settle fulfilsThe first to settle rejects
Promise.anyThe first to fulfilAll reject

Use all for independent requests that are all needed, allSettled when some may fail and you want every result, race for timeouts, and any for "whichever mirror answers first".

Ready-made promises

Promise.resolve(value) and Promise.reject(error) create already-settled promises — useful for returning cached values from a function that normally returns a promise. Promise.withResolvers() returns a promise together with its resolve and reject functions, for cases where they're needed outside the executor.

Unhandled rejections

A rejected promise with no catch produces an "Uncaught (in promise)" error in the browser console; in Node.js it can crash the process. Always end a promise chain with .catch(), or await it inside try/catch.

Try it yourself

Try it yourself
Console output