Of course I have a backup!

Random blobs of wisdom about software development

Async/await destroys stack traces in Node

Saturday, September 29, 2018

And it's a terrible mess, and nobody should use it until this is fixed. Yeah I said it. The internet is already full of "5 reasons async/await blows Promises away" and "How to escape callback hell with async/await", only thing they all fail to mention is that is that it will lead to errors that are missing crucial stack trace information.

The following example code produces the correct stack trace:

const f1 = v => f2(v);
const f2 = v => f3(v);
const f3 = v => f4(v);
const f4 = v => {
    throw new Error(`Problem: ${v}`);
}
f1("hello");

Produces:

(node:21652) UnhandledPromiseRejectionWarning: Error: Problem: hello
    at f4 (/home/myuser/projects/asyncpoc/index.js:5:11)
    at f3 (/home/myuser/projects/asyncpoc/index.js:3:17)
    at f2 (/home/myuser/projects/asyncpoc/index.js:2:17)
    at f1 (/home/myuser/projects/asyncpoc/index.js:1:79)
    at Object.<anonymous> (/home/myuser/projects/asyncpoc/index.js:8:1)
    at Module._compile (internal/modules/cjs/loader.js:689:30)
    at Object.Module._extensions..js (internal/modules/cjs/loader.js:700:10)
    at Module.load (internal/modules/cjs/loader.js:599:32)
    at tryModuleLoad (internal/modules/cjs/loader.js:538:12)
    at Function.Module._load (internal/modules/cjs/loader.js:530:3)

The first 4 lines is the valuable information we need. Now let's change f4 to be async, and have an await statement, and re-run it:

const f1 = v => f2(v);
const f2 = v => f3(v);
const f3 = v => f4(v);
const f4 = async v => {
    await Promise.resolve();
    throw new Error(`Problem: ${v}`);
}

f1("hello");

Output:

(node:22122) UnhandledPromiseRejectionWarning: Error: Problem: hello
    at f4 (/home/myuser/projects/asyncpoc/index.js:6:11)
    at process._tickCallback (internal/process/next_tick.js:68:7)
    at Function.Module.runMain (internal/modules/cjs/loader.js:745:11)
    at startup (internal/bootstrap/node.js:266:19)
    at bootstrapNodeJSCore (internal/bootstrap/node.js:596:3)

The first line is the only somewhat relevant information, which points to the line where the throw happened, it's missing crucial calls from before it, and everything else is completely useless. It took me an hour of debugging to narrow down the code what is causing this, and it's the first await call. I found a github issue opened on Node from 2017 March 15, and it's closed because it has to do with V8, and there is nothing to be done in Node. This is absolutely fucking outragous, it's a serious bug, and it is already part of the language since Node 8, but nobody from the developers think it's serious enough, that this "feature" should maybe stay hidden behind a feature flag, or show some warnings that you should not use this if you care about debugging. The only indication of this behavior is in a closed github ticket. You call some 3rd party library that internally uses async/await, and you are now subject to this.

I'd much rather have the promise chaining syntax in my code, than errors that don't show where they are coming from.

This was written by Norbert Kéri, posted on Saturday, September 29, 2018, at 18:06

Tagged as:

Post a comment

Providing your email is optional, it is never published or shared, it is only used for auto approval purposes. If you already have at least 1 approved comment(s) tied to your email, you don't have to wait for moderation, otherwise the author must approve your comment.

Please solve this totally random captcha