How does promise chaining work?
What promise chaining is
Promise chaining is the sequential execution of asynchronous actions,
where the result of one .then() is passed to the next.
Every .then(), .catch() and .finally() returns a new promise,
which makes it possible to build a linear processing flow - without "callback hell".
Basic chain example
new Promise((resolve) => {
setTimeout(() => resolve(10), 1000);
})
.then((num) => {
console.log('1. Got:', num);
return num * 2;
})
.then((num) => {
console.log('2. Multiplied:', num);
return num + 5;
})
.then((result) => {
console.log('3. Result:', result);
});Output:
1. Got: 10
2. Multiplied: 20
3. Result: 25Each .then():
- receives the result of the previous step,
- returns a new promise,
- passes its result further down the chain.
How it works under the hood
- The first promise is created and moves to
pending. Whenresolve(value)is called, it becomesfulfilled. - The first
.then()receivesvalue, and returns a new promise, which now waits for the next result. - Every
.then()in the chain waits for the previous one to finish, even if a new promise is returned inside it.
Example with an async step
Promise.resolve(1)
.then((n) => {
console.log('A:', n);
return new Promise((resolve) =>
setTimeout(() => resolve(n + 1), 1000)
);
})
.then((n) => {
console.log('B:', n);
return n * 3;
})
.then((n) => console.log('C:', n));Output (after one second):
A: 1
B: 2
C: 6Here:
.then()"recognizes" that a new promise was returned to it,- waits for it to finish (
resolve), - and only then runs the next step of the chain.
Errors in the chain
If an error occurs in any .then() (throw or reject),
all following .then() calls are skipped,
and control passes to the nearest .catch().
Promise.resolve(10)
.then((x) => x / 2)
.then(() => {
throw new Error('Error!');
})
.then(() => console.log('will not run'))
.catch((err) => console.error('Caught:', err.message))
.then(() => console.log('continuing'));Output:
Caught: Error!
continuingReturning values and promises from .then()
What .then() returns | What happens |
|---|---|
| A plain value | It gets wrapped in Promise.resolve(value) |
| A promise | The chain waits for it to finish |
| An error (throw) | The chain moves to rejected |
| Nothing | undefined is passed further |
Combination example
Promise.resolve('A')
.then((v) => v + 'B') // returns 'AB'
.then((v) => Promise.resolve(v + 'C')) // waits for another promise
.then((v) => v + 'D')
.then(console.log); // ABDC.catch() can be used in the middle of a chain
Promise.reject('Error')
.catch((err) => {
console.log('Caught:', err);
return 'Recovered';
})
.then((v) => console.log('Next:', v));Output:
Caught: Error
Next: Recovered.catch() also returns a new promise,
so the chain continues after the error is handled.
.finally() in a chain
finally() always runs, regardless of the result,
and it does not change the value (unless it throws an error):
Promise.resolve('OK')
.finally(() => console.log('Cleaning up...'))
.then(console.log);Output:
Cleaning up...
OKExample of a full chain with errors
Promise.resolve(1)
.then((x) => x + 1)
.then(() => {
throw new Error('Oops!');
})
.catch((e) => {
console.log('Error caught:', e.message);
return 5;
})
.then((x) => console.log('Result:', x))
.finally(() => console.log('Done'));Output:
Error caught: Oops!
Result: 5
DoneA simple analogy
Think of a promise chain as an assembly line.
- Each
.then()is a station, where the part gets processed; - If something goes wrong,
.catch()fixes the error and puts the part back on the line; - At the end,
.finally()turns off the machines and sweeps the floor.
Summary
| Feature | Description |
|---|---|
Every .then() | Returns a new promise |
| Values flow downward | The result from .then() goes into the next one |
| Errors | Propagate down to the nearest .catch() |
.finally() | Runs no matter what |
| Chaining | Lets you write async code linearly, without nesting |
Short Answer
Interview readyA concise answer to help you respond confidently on this topic during an interview.