LearnFrenzy
Sign in Take a free test

Asynchronous JavaScript · Lesson 25 of 40

JavaScript Promises

5 min read Updated Nov 7, 2026 Saurabh Samir

A promise is an object representing a value that isn't available yet – the result of a network request, a file read or a timer. Instead of passing callbacks into a function, the function returns a promise, and you attach what should happen when it succeeds or fails. Promises are the foundation of async/await and of every modern web API.

In this lesson

  • The problem: callback hell
  • Promise states and creating a promise
  • then, catch, finally and chaining
  • Running promises together: all, allSettled, race, any

The problem promises solve

With callbacks, each dependent step nests inside the previous one, and every level needs its own error handling:

getUser(1, (err, user) => {
  if (err) return showError(err);
  getOrders(user.id, (err, orders) => {
    if (err) return showError(err);
    getInvoice(orders[0].id, (err, invoice) => {
      if (err) return showError(err);
      console.log(invoice);            // the "pyramid of doom"
    });
  });
});

With promises the same flow is a flat chain with one error handler – and with await (next lesson) it reads like normal code.

States of a promise

A promise is always in one of three states

pending

  • fulfilledresolve(value) was called → then callbacks run
  • rejectedreject(error) or a throw → catch callbacks run

Once a promise settles (fulfilled or rejected) it never changes again.

Creating a promise

function checkStock(item, qty) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {                    // pretend this is a server call
      const available = { pen: 50, bag: 2 };
      if (!(item in available)) reject(new Error(`Unknown item: ${item}`));
      else if (available[item] < qty) reject(new Error(`Only ${available[item]} ${item}s left`));
      else resolve({ item, qty, reserved: true });
    }, 50);
  });
}

checkStock("pen", 10)
  .then((result) => console.log("OK:", result))
  .catch((err) => console.log("Failed:", err.message));

checkStock("bag", 5)
  .then((result) => console.log("OK:", result))
  .catch((err) => console.log("Failed:", err.message))
  .finally(() => console.log("Stock check finished"));

Output

OK: { item: 'pen', qty: 10, reserved: true }
Failed: Only 2 bags left
Stock check finished

In practice you rarely write new Promise yourself – fetch, database drivers and most libraries already return promises. You'll use it mainly to wrap old callback APIs.

Chaining

Each then returns a new promise resolved with whatever its callback returns. If the callback returns a promise, the chain waits for it:

const wait = (ms, value) => new Promise((r) => setTimeout(() => r(value), ms));
const getUser = (id) => wait(30, { id, name: "Asha" });
const getOrders = (user) => wait(30, [{ id: 101, total: 1200 }, { id: 102, total: 450 }]);

getUser(7)
  .then((user) => {
    console.log("User:", user.name);
    return getOrders(user);              // return the promise – the chain waits
  })
  .then((orders) => orders.reduce((sum, o) => sum + o.total, 0))   // return a value
  .then((total) => console.log("Lifetime spend:", total))
  .catch((err) => console.log("Something failed:", err.message));

Output

User: Asha
Lifetime spend: 1650

Common mistake: forgetting to return

If a then callback starts another async operation but doesn't return it, the next then runs immediately with undefined, and errors from that operation escape the chain's catch.

Error handling

Promise.resolve(10)
  .then((n) => {
    if (n > 5) throw new Error("Too big");    // a throw rejects the chain
    return n;
  })
  .then(() => console.log("skipped"))          // skipped after a rejection
  .catch((err) => {
    console.log("Caught:", err.message);
    return 5;                                  // recover with a fallback
  })
  .then((n) => console.log("Continuing with", n));

Output

Caught: Too big
Continuing with 5

A rejection skips every then until the nearest catch. Always end a chain with catch – an unhandled rejection is logged as an error in browsers and crashes a Node.js process.

Running promises together

const wait = (ms, value, fail = false) =>
  new Promise((res, rej) => setTimeout(() => (fail ? rej(new Error(value)) : res(value)), ms));

const [profile, courses] = await Promise.all([wait(40, "profile"), wait(20, "courses")]);
console.log("all:", profile, courses);

try {
  await Promise.all([wait(10, "ok"), wait(20, "server down", true)]);
} catch (e) {
  console.log("all failed fast:", e.message);
}

const results = await Promise.allSettled([wait(10, "ok"), wait(20, "timeout", true)]);
console.log(results.map((r) => r.status === "fulfilled" ? r.value : `error: ${r.reason.message}`));

console.log("race:", await Promise.race([wait(50, "slow"), wait(10, "fast")]));
console.log("any:", await Promise.any([wait(10, "mirror A down", true), wait(30, "mirror B")]));

Output

all: profile courses
all failed fast: server down
[ 'ok', 'error: timeout' ]
race: fast
any: mirror B
MethodResolves whenRejects whenUse for
Promise.allall succeed (results in order)any one failsloading data you need all of
Promise.allSettledall have finishedneverindependent jobs; report each result
Promise.racefirst to settle succeedsfirst to settle failstimeouts
Promise.anyfirst successall failfastest of several mirrors

Practice

Try it yourself

  1. Write withTimeout(promise, ms) that rejects with "Timed out" if the promise takes longer than ms.

    Show solution
    const wait = (ms, v) => new Promise((r) => setTimeout(() => r(v), ms));
    function withTimeout(promise, ms) {
      let timer;
      const timeout = new Promise((_, reject) => {
        timer = setTimeout(() => reject(new Error("Timed out")), ms);
      });
      return Promise.race([promise, timeout]).finally(() => clearTimeout(timer));
    }
    withTimeout(wait(20, "fast data"), 100).then(console.log);
    withTimeout(wait(200, "slow data"), 50).catch((e) => console.log(e.message));

    Output

    fast data
    Timed out

Quick check

What are the three states of a promise?

Pending, fulfilled and rejected.

What does .then() return?

A new promise, resolved with the callback's return value (or following a returned promise).

Promise.all vs Promise.allSettled?

all rejects as soon as one fails; allSettled waits for all and reports each outcome.

Summary

  • A promise is pending, then fulfilled or rejected – once.
  • then returns a new promise; return values and promises to keep chains flat; end with catch.
  • Combine with all, allSettled, race and any.

Finished this lesson?

Mark it complete to track your progress, then carry on with the next lesson.

Next lesson

Search LearnFrenzy

Esc

Quick links