JavaScript Promise `catch` and `finally` Semantics
`catch` handles a Promise rejection and returns a new Promise, which may remain rejected or recover to fulfillment. `finally` runs after either fulfillment or rejection and usually preserves the original outcome unless its callback throws or returns a rejected Promise.
`promise.catch(onRejected)` is shorthand for `promise.then(undefined, onRejected)`. The rejection handler receives the rejection reason. If it returns a value, the Promise returned by `catch` fulfills with that value; if it throws or returns a rejected Promise, the returned Promise rejects instead.
A `catch` handler can therefore recover from an error, allowing later fulfillment handlers to run. If it does not recover, the rejection continues down the chain until a handler deals with it. Each `then`, `catch`, or `finally` call creates a new Promise, so handle the Promise returned by the chain rather than assuming the original one changed.
`promise.finally(onFinally)` invokes `onFinally` after the original Promise fulfills or rejects. The callback receives no fulfillment value or rejection reason. If it completes normally, the Promise returned by `finally` keeps the original outcome: its value on fulfillment or its reason on rejection.
If the `finally` callback throws, or returns a Promise that rejects, that new error or rejection replaces the original outcome. If it returns a pending Promise, the chain waits for it to settle before continuing. Use `finally` for cleanup that should happen in either case, and `catch` when you need to inspect, report, or recover from a rejection.
Key points
- `catch(handler)` is equivalent to `then(undefined, handler)`.
- A `catch` handler that returns normally can convert rejection into fulfillment.
- `finally` runs after either fulfillment or rejection and receives no outcome argument.
- A normally completing `finally` callback preserves the original value or rejection reason.
- A throw or rejected Promise from `finally` overrides the original outcome.
Interview questions
What does `promise.catch(handler)` do?
`catch(handler)` registers a handler for rejection. It is equivalent to `promise.then(undefined, handler)`. The call returns a new Promise, so the handler's result determines the next outcome. Returning a value fulfills that new Promise; throwing or returning a rejected Promise keeps it rejected.
Can a `catch` handler recover from a rejection?
Yes. If the handler returns normally, the Promise returned by `catch` fulfills with the returned value. Later fulfillment handlers can then run. If the handler throws or returns a rejected Promise, the rejection continues instead.
What does `finally` do, and does it change the Promise's value?
`finally` runs its callback after the Promise fulfills or rejects. The callback is called without the fulfillment value or rejection reason. If it completes normally, the Promise returned by `finally` preserves the original value or rejection reason. If it throws or returns a rejected Promise, that new failure becomes the outcome.
When would you use `finally` instead of `catch`?
Use `finally` for cleanup or completion work that must happen whether the operation succeeds or fails, such as clearing a loading indicator. Use `catch` when you need to inspect a rejection, report it, or recover from it. A `finally` callback cannot directly inspect the rejection reason because it receives no arguments. Be careful that a throw or rejection during cleanup can replace the original error.