A normally completed finally callback preserves the prior promise outcome. Use then to transform a fulfilled value and catch for an intentional recovery; reserve finally for outcome-independent cleanup.
Reproduce the behavior safely
Run this self-contained fixture in an empty temporary directory, separate from your project. Shell examples create only named fixture files there. JavaScript examples use local values and send no network requests.
const assert = require('node:assert/strict');
(async () => {
assert.equal(await Promise.resolve(5).finally(() => 90), 5);
assert.equal(await Promise.resolve(5).then(() => 90), 90);
const original = new Error('work failed');
await assert.rejects(
Promise.reject(original).finally(() => 90),
error => error === original
);
await assert.rejects(
Promise.resolve(5).finally(() => { throw new Error('cleanup failed'); }),
/cleanup failed/
);
console.log('finally preserved success/rejection; cleanup throw replaced outcome');
})().catch(e => { console.error(e); process.exitCode = 1; });Observed local output
finally preserved success/rejection; cleanup throw replaced outcomeWhy the result changes
Returning ninety from finally does not replace five, and a successful cleanup does not turn a rejection into success. Throwing during cleanup is different: it can make the resulting promise reject with the cleanup failure. The callback receives no fulfillment-value or rejection-reason parameter, so code expecting that argument is already working under the wrong contract.
Choose and verify the correction
Put value transformation in then or a clear async sequence. Put deliberate fallback logic in catch and preserve unexpected errors. Use finally for closing or releasing resources owned by the operation, and decide how cleanup failures should be reported without hiding the original failure. If cleanup is asynchronous, return or await its promise so the caller owns completion. Keep a copy of the original error when both failures must remain visible.
Limits and next checks
The fixture uses native promises without external resources. Promise finally differs from a return statement inside a language try/finally block; do not transfer that control-flow rule to this method. Releasing a shared resource also needs ownership rules beyond attaching a callback. The example verifies outcome behavior, not cancellation of sibling work or a transaction rollback.
Related: Async Filter Keeps Rejected Items: Resolve Decisions Before Filtering.
Isolated fixture executed on macOS on November 10, 2026 (KST), with v26.5.0. The observed output verifies this example, not every application or runtime.