Skip to content

JavaScript Tutorial Asynchronous JavaScript

Promise then(), catch() and finally()

Lesson 143 of 258 2 min read By Deepak Paswan Updated Oct 4, 2026

then

then(onFulfilled) runs when the promise succeeds. It receives the result.

script.js
JS
Promise.resolve(5).then(value => console.log("value:", value)); // → value: 5

catch

catch(onRejected) runs when the promise fails, or when any earlier handler throws.

script.js
JS
Promise.reject(new Error("Boom")).catch(error => console.log("caught:", error.message)); // → caught: Boom

finally

finally(fn) runs either way, for cleanup such as hiding a spinner. It receives no arguments and does not change the result.

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

console.log("show spinner");
wait(200)
  .then(() => console.log("loaded"))
  .catch(error => console.log("failed"))
  .finally(() => console.log("hide spinner"));

Chaining

Every then returns a new promise, so calls can be chained. What your handler returns becomes the next step’s value.

script.js
JS
Promise.resolve(2)
  .then(n => n * 10)
  .then(n => n + 1)
  .then(n => console.log(n)); // → 21

If a handler returns another promise, the chain waits for it:

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

wait(100)
  .then(() => { console.log("step 1"); return wait(100); })
  .then(() => { console.log("step 2"); return "all done"; })
  .then(result => console.log(result)); // → all done

Errors skip ahead

An error (a rejection or a thrown error) skips the then handlers until the next catch. After the catch, the chain continues normally.

script.js
JS
Promise.resolve()
  .then(() => { throw new Error("step 1 failed"); })
  .then(() => console.log("skipped"))
  .catch(error => { console.log("caught:", error.message); return "recovered"; })
  .then(value => console.log(value)); // → recovered

A very common mistake: forgetting return

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

wait(50)
  .then(() => { wait(50); })          // missing return: the next step does not wait
  .then(result => console.log(result)); // → undefined

Without return, the chain does not wait for the inner promise, and the value is lost.

Always handle rejections

A rejected promise with no catch becomes an unhandled rejection, which shows as an error in the console. End chains with .catch(...).

lightbulb
Next
Chains are readable, but async / await makes the same code look like ordinary step-by-step code: async and await.