Skip to content

Intermediate · Asynchronous JavaScript · topic 37 of 61 · 30 min

Promises

Explanation

A promise is an object that stands for a result that will arrive later. It is pending at first, then becomes fulfilled with a value or rejected with an error, exactly once. You attach then for success, catch for failure and finally for clean-up, and you can chain thens so each step waits for the one before.

What a promise is

A promise is a placeholder object for a future value. When you call an async function that returns a promise, you get the promise immediately, and its result fills in later. It is always in one of three states: pending (still waiting), fulfilled (done, with a value) or rejected (failed, with a reason, usually an Error). Once fulfilled or rejected it is settled and never changes again.

Think of it like this: A promise is like the order slip you get at a tailor's shop. Today you only hold the slip (pending). Later the shirt is either ready (fulfilled) or the tailor tells you the cloth was damaged (rejected). Either way, it happens once, and the slip tells you what to do in each case.

Using a promise: then, catch, finally

javascript
function getFare(train) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      if (train === '12951') resolve(3200);
      else reject(new Error(`Unknown train ${train}`));
    }, 50);
  });
}

getFare('12951')
  .then((fare) => console.log('Fare:', fare))       // Fare: 3200
  .catch((err) => console.log('Error:', err.message))
  .finally(() => console.log('Hide spinner'));

Creating a promise

new Promise((resolve, reject) => { … }) runs the function immediately. Call resolve(value) to fulfil or reject(error) to reject. Only the first call counts. Most of the time you will USE promises from APIs (fetch, fs/promises) rather than create them, but wrapping an old callback API is a common reason to create one.

javascript
function wait(ms) {
  return new Promise((resolve) => setTimeout(resolve, ms));
}
wait(100).then(() => console.log('100 ms later'));

Chaining

then returns a NEW promise. Whatever your then callback returns becomes the value for the next then. If it returns a promise, the chain waits for it. This turns callback hell into a flat list of steps.

javascript
getUser(7)
  .then((user) => getOrders(user.id))      // returns a promise; the chain waits
  .then((orders) => getInvoice(orders[0].id))
  .then((invoice) => sendEmail(invoice))
  .then(() => console.log('Done!'))
  .catch((err) => console.log('Something failed:', err.message)); // one catch for every step

Errors travel down the chain

If any step rejects or throws, the following thens are skipped until a catch is found. A catch that returns a value "recovers": the chain continues as fulfilled after it.

javascript
Promise.resolve(10)
  .then((n) => { throw new Error('boom'); })
  .then(() => console.log('skipped'))
  .catch((e) => { console.log('caught', e.message); return 0; })
  .then((n) => console.log('continues with', n));
// caught boom
// continues with 0
Careful: Forgetting to RETURN inside then breaks the chain: .then((user) => { getOrders(user.id); }) gives undefined to the next step, which then runs before the orders arrive. With braces, always write return; or use an arrow without braces.

Turning callbacks into promises

javascript
function readFileAsync(path) {
  return new Promise((resolve, reject) => {
    require('node:fs').readFile(path, 'utf8', (err, text) => (err ? reject(err) : resolve(text)));
  });
}
// Node already provides this: require('node:fs/promises').readFile, and util.promisify for others.
Tip: An unhandled rejected promise prints "Uncaught (in promise)" in the browser, and in recent Node versions it crashes the process. Always end a chain with catch, or await it inside try/catch.

Need help?

Stuck on a word or an idea? Search this course, or every course, and jump straight to the explanation or a worked question.

Try:

Watch the lesson

Promises · 1/7

Promises

In this lesson you will use promises, objects that stand for a result that arrives later.

Prefer a video by a teacher? Find free videos on “javascript promises then catch chaining explained” on YouTube ↗

In real life: Checking out on a shopping app

Checkout involves several async steps: reserve stock, charge the payment, create the order. With a promise chain each step waits for the previous one, and a single catch shows "Payment failed, nothing was charged" if any step fails.

javascript
const step = (name, ok = true) => new Promise((res, rej) => setTimeout(() => (ok ? res(name) : rej(new Error(name + ' failed'))), 10));
step('reserve')
  .then(() => step('charge', false))
  .then(() => step('create order'))
  .then(() => console.log('Order placed'))
  .catch((e) => console.log(e.message)); // charge failed

Practice (0/3 done)

Task 1 · Code · Javascript

Write wait(ms) that returns a promise that fulfils (with undefined) after ms milliseconds.

Task 2 · Code · Javascript

Write checkPin(pin) that returns a promise: it fulfils with "ok" when pin is a 4-digit string, and rejects with an Error "Invalid PIN" otherwise.

Task 3 · Code · Javascript

Fix the chain in totalForUser: the second then does not return, so the total arrives too late. getUser and getOrders return promises.

Quick quiz

Question 1

1.What are the three states of a promise?

Question 2

2.Promise.resolve(2).then((n) => n * 3).then((n) => n + 1) fulfils with…

Question 3

3.If a then callback throws, what runs next?

Question 4

4.new Promise((res) => { res(1); res(2); }) fulfils with…

Read more (free)

Open a question to see the answer and why. The first ones are this topic's own quiz, so try the quiz above before you peek.

What are the three states of a promise?

Answer: pending, fulfilled, rejected

A promise is pending until it is fulfilled or rejected.

Promise.resolve(2).then((n) => n * 3).then((n) => n + 1) fulfils with…

Answer: 7

2 × 3 = 6, then 6 + 1 = 7.

If a then callback throws, what runs next?

Answer: The nearest catch

A throw turns into a rejection that skips to catch.

new Promise((res) => { res(1); res(2); }) fulfils with…

Answer: 1

Only the first resolve or reject counts.

What does this print? console.log('A'); setTimeout(() => console.log('B'), 0); Promise.resolve().then(() => console.log('C')); console.log('D');Related

Answer: A D C B

Normal code runs first (A, D). Then the microtask queue runs (C), and only then the timer task (B), even with 0 ms.

From Promises and the event loop

In an error-first callback (err, data), what should you do first?Related

Answer: Check err

If err is set, data is not valid.

From Why async, and callbacks

Three independent requests each take about 1 second. How long does await Promise.all([a(), b(), c()]) take?Related

Answer: About 1 second

Promise.all starts them together, so the total is about the slowest one. Awaiting them one by one would take about 3 seconds.

From async/await and error handling

How do you handle a rejected await?Related

Answer: try/catch around the await

A rejection is thrown at the await, so catch it.

From async and await

Pass the quiz (60% or more) or mark this topic as done when you are ready.

Your progress is saved on this device only.

Next steps

  1. 4 more topics in “Asynchronous JavaScript” before its exam: async and await, Promise.all, allSettled, race and any, The event loop, fetch: talking to servers.
  2. Module 8 exam: Asynchronous JavaScript · 12 questions · 25 min, without help. 6 topics of this module still to finish first.
  3. Then the next module, TypeScript basics. 61 topics left in the course before the final exam.

← Previous: Why async, and callbacks