then
then(onFulfilled) runs when the promise succeeds. It receives the result.
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.
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.
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.
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:
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.
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
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(...).
Next
async / await makes the same code look like ordinary step-by-step code: async and await.