Skip to content

Promise .finally gives UnhandledPromiseRejection even if .catch has been used. #28636

Description

@SergeyFromHell

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.

Activity

  1. Hakerh400 commented on Jul 11, 2019

    @Hakerh400
    Contributor

    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:

    const p = Promise.reject(new Error('BOOM'));
    p.catch(() => {}).finally(() => {});

    Similar question was asked before: nodejs/help#1208

  2. MuraliSRao commented on Jul 13, 2019

    @MuraliSRao

    Catch is simply to catch the errors.
    As a defintion of finally, - the finally block must excute at least once in whatever situation.

  3. targos commented on Jul 21, 2019

    @targos
    Member

    @Hakerh400's explanation is correct.

  4. jedwards1211 commented on Jun 15, 2021

    @jedwards1211

    @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.

  5. targos commented on Jun 16, 2021

    @targos
    Member

    I don't know if Node.js could do something like that, but I find the behavior consistent with the equivalent synchronous code:

    try {
      throw new Error('boom')
    } finally {
      console.log('hello')
    }

    This will log "hello" and also leave an unhandled exception.

  6. jedwards1211 commented on Jun 17, 2021

    @jedwards1211

    @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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    promisesIssues and PRs related to ECMAScript promises.

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions