You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Promise .finally gives UnhandledPromiseRejection even if .catch has been used. #28636
Version: v12.6.0 (but 10+ also has same behaviour)
Platform: Linux sf2 4.15.0-52-generic #56-Ubuntu SMP Tue Jun 4 22:49:08 UTC 2019 x86_64 x86_64 x86_64 GNU/Linux
Subsystem:Promises
If one use promise.finally(..), it leads to unhandled promise rejection warning despite the .catch(..) handler also used.
Example code: const p = Promise.reject(new Error('BOOM')); p.catch(() => {}); p.finally(() => {});
Output: (node:22503) UnhandledPromiseRejectionWarning: Error: BOOM at Object.<anonymous> (/home/user/other/t1/t.js:25:26) at Module._compile (internal/modules/cjs/loader.js:776:30) at Object.Module._extensions..js (internal/modules/cjs/loader.js:787:10) at Module.load (internal/modules/cjs/loader.js:643:32) at Function.Module._load (internal/modules/cjs/loader.js:556:12) at Function.Module.runMain (internal/modules/cjs/loader.js:839:10) at internal/main/run_main_module.js:17:11 (node:22503) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). (rejection id: 3) (node:22503) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.
If you comment last line with .finally handler, no warnings issued.
That is how promises work. You defined catch and finally handlers on the same promise. The promise then follows both "paths" when resolving/rejecting. One path catches it, but the other contains only finally, making the rejection pass through it, then leading to unhandled rejection. What you probably want is to define finally on the promise returned by the catch call, for example:
@targos I just ran into this and it was a really nasty surprise, had me staring at my screen thinking wtf for 30 minutes, even though I've been using promises for years and years. I think the intended behavior could be changed to be more humane.
Is there no way that node could internally flag the promise returned by .finally such that it won't trigger an unhandled rejection like other promises, as long as the finally handler doesn't throw its own error? It would alleviate a lot of suffering.
If there's no catch the original promise would still trigger an unhandled rejection as desired. But unhandled rejections coming from a non-throwing .finally don't provide any concrete benefit that I can imagine. I think they just cause harm.
@targos yes I know, but consider this difference -- unlike sync code where you only need to catch an error once, with async code you can end up having to catch the same error multiple times to prevent unhandled rejection, even if only one of those catch handlers actually needs to do anything about it. .finally() can make the same error rear its head after you've already handled it elsewhere.
I realize now, the problem can happen with .then() too. I wish Node would just help me make sure I catch a given error at least once, rather than every place it gets "passed through" to the next link in the promise chain.
Because if I create a dead-end .finally(), I was intending to respond to errors elsewhere, so the error handler for the dead-end is guaranteed to be a no-op.
Activity
Hakerh400 commentedon Jul 11, 2019
That is how promises work. You defined
catchandfinallyhandlers on the same promise. The promise then follows both "paths" when resolving/rejecting. One path catches it, but the other contains onlyfinally, making the rejection pass through it, then leading to unhandled rejection. What you probably want is to definefinallyon the promise returned by thecatchcall, for example:Similar question was asked before: nodejs/help#1208
MuraliSRao commentedon Jul 13, 2019
Catch is simply to catch the errors.
As a defintion of finally, - the finally block must excute at least once in whatever situation.
targos commentedon Jul 21, 2019
@Hakerh400's explanation is correct.
jedwards1211 commentedon Jun 15, 2021
@targos I just ran into this and it was a really nasty surprise, had me staring at my screen thinking wtf for 30 minutes, even though I've been using promises for years and years. I think the intended behavior could be changed to be more humane.
Is there no way that node could internally flag the promise returned by
.finallysuch that it won't trigger an unhandled rejection like other promises, as long as the finally handler doesn't throw its own error? It would alleviate a lot of suffering.If there's no
catchthe original promise would still trigger an unhandled rejection as desired. But unhandled rejections coming from a non-throwing.finallydon't provide any concrete benefit that I can imagine. I think they just cause harm.targos commentedon Jun 16, 2021
I don't know if Node.js could do something like that, but I find the behavior consistent with the equivalent synchronous code:
This will log "hello" and also leave an unhandled exception.
jedwards1211 commentedon Jun 17, 2021
@targos yes I know, but consider this difference -- unlike sync code where you only need to catch an error once, with async code you can end up having to catch the same error multiple times to prevent unhandled rejection, even if only one of those catch handlers actually needs to do anything about it.
.finally()can make the same error rear its head after you've already handled it elsewhere.I realize now, the problem can happen with
.then()too. I wish Node would just help me make sure I catch a given error at least once, rather than every place it gets "passed through" to the next link in the promise chain.Because if I create a dead-end
.finally(), I was intending to respond to errors elsewhere, so the error handler for the dead-end is guaranteed to be a no-op.