Skip to content

No correct stack trace when reject value isn't an instanceof Error #41676

Closed
@ItamarGronich

Description

@ItamarGronich

Version

15.x.x, 16.x.x, 17.x.x

Platform

Darwin ItamarGro-2.local 20.6.0 Darwin Kernel Version 20.6.0: Mon Aug 30 06:12:21 PDT 2021; root:xnu-7195.141.6~3/RELEASE_X86_64 x86_64

Subsystem

No response

What steps will reproduce the bug?

Node doesn't seem to add relevant stack trace information to where the error occurred in the case of unhandled rejection with a value that is not instance of Error.

This is a case that reproduces this undesired behavior:
run this with node index.js:

// index.js
function notOk() {
  return Promise.reject("Uh-oh!");
}

notOk();

The output of this script is:

node:internal/process/promises:246
          triggerUncaughtException(err, true /* fromPromise */);
          ^

[UnhandledPromiseRejection: 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(). The promise rejected with the reason "Uh-oh!".] {
  code: 'ERR_UNHANDLED_REJECTION'
}

This is problematic because in the output above there's simply no information to help me understand where the error occurred.
In such a small script it is obvious where the problem is but in a larger codebase it's basically impossible to find.
I found it only by debugging and running through the functions.

In contrast, rejecting with a value that is an instance of Error will throw a stack trace correctly:

// index.js
function ok() {
  return Promise.reject(new Error("OK!"));
}

ok();

This script will output:

/Users/itamar/projects/test/index.js:2
  return Promise.reject(new Error("OK!"));
                        ^

Error: OK!
    at ok (/Users/itamar/projects/test/index.js:2:25)
    at Object.<anonymous> (/Users/itamar/projects/test/index.js:5:1)
    at Module._compile (node:internal/modules/cjs/loader:1101:14)
    at Object.Module._extensions..js (node:internal/modules/cjs/loader:1153:10)
    at Module.load (node:internal/modules/cjs/loader:981:32)
    at Function.Module._load (node:internal/modules/cjs/loader:822:12)
    at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:81:12)
    at node:internal/main/run_main_module:17:47

In this case i know where the problem is and i can fix it.

How often does it reproduce? Is there a required condition?

It reproduces in node 15, 16 and 17 (with default value for flag --unhandled-rejections=throw and not --unhandled-rejections=warn-with-error-code or some other option) on every unhandled rejection with a value that is not instance of error.

What is the expected behavior?

It would be nice to at least get a stack trace to the place in my code where the reject happened.
something like this:

/Users/itamar/projects/test/index.js:2
  return Promise.reject("Uh-oh!");
                        ^

UnhandledPromiseRejection: 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(). The promise rejected with the reason "Uh-oh!"
    at ok (/Users/itamar/projects/test/index.js:2:25)
    at Object.<anonymous> (/Users/itamar/projects/test/index.js:5:1)
    at Module._compile (node:internal/modules/cjs/loader:1101:14)
    at Object.Module._extensions..js (node:internal/modules/cjs/loader:1153:10)
    at Module.load (node:internal/modules/cjs/loader:981:32)
    at Function.Module._load (node:internal/modules/cjs/loader:822:12)
    at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:81:12)
    at node:internal/main/run_main_module:17:47

What do you see instead?

I see a non descriptive error message that doesn't explain clearly where the problem is:

node:internal/process/promises:246
          triggerUncaughtException(err, true /* fromPromise */);
          ^

[UnhandledPromiseRejection: 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(). The promise rejected with the reason "Uh-oh!".] {
  code: 'ERR_UNHANDLED_REJECTION'
}

Additional information

Basically the mechanism for throwing on uncaught rejections is nice but i think it's missing handling those edge cases where reject value isn't a native error.

Metadata

Metadata

Assignees

No one assigned

    Labels

    feature requestIssues that request new features to be added to Node.js.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