Skip to content
Laviyx Learn

Try

JavaScript course menu

JavaScript Promises: then, catch, finally, Chaining and Promise.all

Learn JavaScript promises: what a promise is, pending, fulfilled and rejected states, creating promises, then, catch and finally, chaining, error handling, and running work in parallel with Promise.all, allSettled, race and any.

Intermediate8 min readUpdated
On this page

A promise is an object that represents a value that is not available yet, such as the response to a network request. It is a placeholder that promises "I will give you a result, or an error, later".

Promise states#

A promise is always in one of three states:

StateMeaning
pendingThe work is still in progress
fulfilledIt finished successfully and has a value
rejectedIt failed and has a reason (usually an Error)

Once a promise is fulfilled or rejected (settled), it never changes again.

Using a promise: then and catch#

Functions such as fetch return promises. You attach callbacks with .then() for success and .catch() for failure. To keep the examples self-contained, this lesson uses a small function that simulates a slow request with a timer:

JavaScript
function getUser(id) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      if (id === 1) resolve({ id: 1, name: "Asha" });
      else reject(new Error(`User ${id} not found`));
    }, 50);
  });
}

getUser(1)
  .then((user) => console.log("Got", user.name))
  .catch((error) => console.log("Failed:", error.message));

getUser(2)
  .then((user) => console.log("Got", user.name))
  .catch((error) => console.log("Failed:", error.message))
  .finally(() => console.log("Request 2 finished"));
Output
Got Asha
Failed: User 2 not found
Request 2 finished
  • .then(callback) runs when the promise is fulfilled, with the value.
  • .catch(callback) runs when it is rejected, with the error.
  • .finally(callback) runs either way, which is useful for hiding a loading spinner.

Creating a promise#

new Promise(executor) takes a function that receives two functions, resolve and reject. Call one of them when the work is done:

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

wait(30).then(() => console.log("30 ms later"));
Output
30 ms later

You will mostly consume promises from APIs rather than create them, but wrapping a callback-based or timer-based function like this is a common need. Promise.resolve(value) and Promise.reject(error) create already-settled promises.

Chaining#

.then() returns a new promise, so steps can be chained. Whatever a then callback returns becomes the value passed to the next one; if it returns a promise, the chain waits for it:

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

wait(20, 5)
  .then((n) => {
    console.log("start with", n);
    return n * 2;                 // a plain value
  })
  .then((n) => {
    console.log("doubled", n);
    return wait(20, n + 1);       // a promise: the chain waits for it
  })
  .then((n) => console.log("finally", n));
Output
start with 5
doubled 10
finally 11

Compare this flat chain with the nested "callback hell" in Callbacks.

Errors in a chain#

A rejection, or an error thrown in any then callback, skips the remaining thens and goes to the next catch:

JavaScript
Promise.resolve(1)
  .then((n) => {
    throw new Error("Something broke at step 2");
  })
  .then(() => console.log("never runs"))
  .catch((error) => console.log("Caught:", error.message))
  .then(() => console.log("The chain continues after catch"));
Output
Caught: Something broke at step 2
The chain continues after catch

Always end a chain with .catch(). An unhandled rejection is reported as an error in the browser console, and in Node.js it stops the program by default.

Running promises in parallel#

When several requests do not depend on each other, start them all at once and wait for the results together:

JavaScript
const wait = (ms, value) => new Promise((resolve) => setTimeout(() => resolve(value), ms));
const fail = (ms, msg) => new Promise((_, reject) => setTimeout(() => reject(new Error(msg)), ms));

Promise.all([wait(30, "profile"), wait(10, "orders"), wait(20, "wishlist")])
  .then((results) => console.log("all:", results));

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

Promise.race([wait(50, "slow"), wait(5, "fast")])
  .then((winner) => console.log("race:", winner));

Promise.any([fail(5, "a"), wait(15, "first success")])
  .then((value) => console.log("any:", value));
Output
race: fast
any: first success
allSettled: [ 'fulfilled', 'rejected' ]
all: [ 'profile', 'orders', 'wishlist' ]
MethodFulfils whenRejects when
Promise.allAll fulfil, with an array of values in the original orderAny rejects (fails fast)
Promise.allSettledAll settle, with { status, value or reason } for eachNever
Promise.raceThe first to settle fulfilsThe first to settle rejects
Promise.anyThe first to fulfilAll reject

Note that Promise.all keeps results in the order you passed them, even though "orders" finished first. The lines print in the order each combined promise settled.

Practice#

Practice exercise

Write delayedSquare(n) that returns a promise resolving to n * n after 20 ms, or rejecting with an Error if n is negative. Use Promise.allSettled on [2, -1, 4] and print each result as either the value or the error message.

Show solution
JavaScript
function delayedSquare(n) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      if (n < 0) reject(new Error(`negative: ${n}`));
      else resolve(n * n);
    }, 20);
  });
}

Promise.allSettled([2, -1, 4].map(delayedSquare)).then((results) => {
  for (const r of results) {
    console.log(r.status === "fulfilled" ? r.value : r.reason.message);
  }
});
Output
4
negative: -1
16

Key takeaway#

Finished this lesson?