Promise.prototype.finally()
For work that must happen either way and should not affect the answer. Its callback receives no arguments, precisely because it is not supposed to care whether things went well.
Demo
The callback returns 999 in every case, and in the first two that return is simply discarded — the original value comes out unchanged. That is the design: cleanup must not be able to alter the answer. The third case is the one exception: a throw inside finally replaces the outcome with a rejection, which is how a failing cleanup step can mask the error that actually mattered.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| onFinally | Function | yes | Called with NO arguments when the promise settles, either way. Its return value is ignored — unless it returns a promise, which is awaited before the chain continues. |
Return value
Promise — A new Promise settling with the ORIGINAL outcome — value or rejection. What the callback returns is discarded; only a throw from it can change the result.
Common patterns
setLoading(true); fetchData().finally(() => setLoading(false));
await useConnection().finally(() => conn.release());
p.finally(cleanup).catch(handle);
Examples
Pitfalls
Promise.reject(new Error("x")).finally(cleanup)
Promise.reject(new Error("x")).finally(cleanup).catch(handle)
await Promise.resolve(1).finally(() => 99)
await Promise.resolve(1).then(() => 99)
Promise.reject(new Error("real")).finally(() => { throw new Error("cleanup"); })
p.finally(() => { try { release(); } catch {} })
await p.finally(() => slowFlush())
await p.finally(() => { void slowFlush(); })
When to use
- Turning off a loading indicator
- Releasing connections, locks and file handles
- Logging that an operation completed, either way
- Anything currently duplicated in both then and catch
- You need to change the result → then or catch
- You need to handle the error → catch, in addition
- The cleanup can itself fail → wrap it, or it masks the real error
- Inside an async function → a try/finally block reads better
Notes
FAQ
No. It observes the settlement and passes it on unchanged, so a rejection remains a rejection. You still need a catch — finally is about cleanup, not handling.
p.finally(cleanup).catch(handle);