简体   繁体   中英

Javascript is losing a backtrace in the catch block

The following code sample:

async function expectResolutionErrorCode(object) {
  try {
    await Promise.resolve(1);
    object.f1() // caught error
  } catch (error) {
    object.f2() // uncaught error
  }
}

expectResolutionErrorCode(undefined).catch(err => console.log(err));

Produces the following backtrace when run using node v14:

$ node  test.ts
TypeError: Cannot read property 'f2' of undefined
    at expectResolutionErrorCode (/Users/bogdan/makabu/unstoppable/resolution/test.ts:6:12)

If I comment out the await statement at line 3 from the sample, the backtrace is complete:

TypeError: Cannot read property 'f2' of undefined
    at expectResolutionErrorCode (/Users/bogdan/makabu/unstoppable/resolution/test.ts:6:12)
    at Object.<anonymous> (/Users/bogdan/makabu/unstoppable/resolution/test.ts:10:1)
    at Module._compile (internal/modules/cjs/loader.js:1201:30)
    at Object.Module._extensions..js (internal/modules/cjs/loader.js:1221:10)
    at Module.load (internal/modules/cjs/loader.js:1050:32)
    at Function.Module._load (internal/modules/cjs/loader.js:938:14)
    at Function.executeUserEntryPoint [as runMain] (internal/modules/run_main.js:71:12)
    at internal/main/run_main_module.js:17:47

So the await statement clearly causes node to lose some part of the backtrace. Why does it behave like that? Is there any workaround to maintain the backtrace while still using await statements inside try catch ?

I tested this behavior in Chrome and Safari and the stacktrace is lost in almost the same way: https://jsfiddle.net/61kdv8fx/

It's because the stack has unwound. Remember that await is syntactic sugar for hooking the settlement of a promise. So when you reach the await in your code, the function returns. Later , when the code continues because the promise was settled, the stack is shallow because promise reactions are called directly.

JavaScript tool makers are very aware of this and working on async call stacks to address it.

However , I should note that when I try to replicate what you're seeing on Node v14, I don't see it, because V8 already has async call stacks. Example:

async function example() {
    try {
        await Promise.resolve(1); // Could just be `await 1;`, but I wanted to match the question
        throw new Error("boom1");
    } catch (e) {
        throw new Error("boom2");
    }
}

async function a() {
    await b();
}

async function b() {
    await c()
}

async function c() {
    await example();
}

a();

When I run it:

$ node example.js 
(node:5342) UnhandledPromiseRejectionWarning: Error: boom2
    at example (/path/to/example.js:6:15)
    at async c (/path/to/example.js:19:5)
    at async b (/path/to/example.js:15:5)
    at async a (/path/to/example.js:11:5)
(Use `node --trace-warnings ...` to show where the warning was created)
(node:5342) 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(). To terminate the node process on unhandled promise rejection, use the CLI flag `--unhandled-rejections=strict` (see https://nodejs.org/api/cli.html#cli_unhandled_rejections_mode). (rejection id: 1)
(node:5342) [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.

I get the same sort of output on modern versions of Chrome and other browsers using a recent V8. But Firefox v78 still loses the context; I'm sure the SpiderMonkey team are working on it.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM