Glossary / Promise chaining
Promise chaining
Promise chaining composes asynchronous steps by returning a Promise from .then, .catch, or .finally. Each handler transforms the prior outcome into the state of a new Promise. Returning values, returning thenables, throwing exceptions, and omitting a return all have distinct consequences; understanding these rules prevents lost ordering and swallowed errors.
Value and error flow
fetchData()
.then((data) => normalize(data)) // return value fulfills next link
.then((model) => persist(model)) // returned promise is adopted
.catch((error) => recover(error)) // return recovers; throw keeps rejection
.finally(() => releaseResources());
If a handler returns a plain value, the next Promise fulfills with it. If it returns a Promise/thenable, the chain waits for and adopts it. If it throws, the next Promise rejects. If it returns nothing, the next Promise fulfills with undefined. A catch that logs but does not rethrow converts failure into fulfillment with undefined, which can make later steps run incorrectly.
finally runs on either fulfillment or rejection and normally preserves the prior outcome. If the finally callback throws or returns a rejected Promise, that new failure replaces the earlier outcome. Always return asynchronous work that must be awaited by the chain; starting a Promise without returning it creates an untracked branch.
Interview questions
- What happens if a
.thenhandler throws? The Promise returned by that.thenrejects. - What if a handler forgets to return a Promise? The chain does not wait for that work.
- Does catch always end the rejection? It recovers only if it returns normally; rethrow to propagate.
- Can finally replace the result? Yes, if it throws or returns a rejected Promise.