Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why do I lose stack trace when using async-await in Node.js?

When I run the following program

async function functionOne() {

  throw new Error('Error here prints the complete stack');

  await new Promise((resolve) => {
    setTimeout(() => { resolve(); }, 1000);
  });
}

async function functionTwo() {
  await functionOne();
}

async function functionThree() {
  await functionTwo();
}

functionThree()
  .catch((error) => {
    console.error(error);
  });

I get the following output which prints the stack through the various invoked functions

Error: Error here prints the complete stack
    at functionOne (/home/divyanshu/programming/errorHandlingAsyncAwait/index.js:3:9)
    at functionTwo (/home/divyanshu/programming/errorHandlingAsyncAwait/index.js:11:9)
    at functionThree (/home/divyanshu/programming/errorHandlingAsyncAwait/index.js:15:9)
    at Object.<anonymous> (/home/divyanshu/programming/errorHandlingAsyncAwait/index.js:18:1)
    at Module._compile (module.js:612:30)
    at Object.Module._extensions..js (module.js:623:10)
    at Module.load (module.js:531:32)
    at tryModuleLoad (module.js:494:12)
    at Function.Module._load (module.js:486:3)
    at Function.Module.runMain (module.js:653:10)

However when the error is thrown after the await call in the following program

async function functionOne() {

  await new Promise((resolve) => {
    setTimeout(() => { resolve(); }, 1000);
  });

  throw new Error('Error here prints incomplete stack');

}

async function functionTwo() {
  await functionOne();
}

async function functionThree() {
  await functionTwo();
}

functionThree()
  .catch((error) => {
    console.error(error);
  });

This is the output

Error: Error here prints incomplete stack
    at functionOne (/home/divyanshu/programming/errorHandlingAsyncAwait/index.js:7:9)
    at <anonymous>

I'd like to understand why the stack trace is lost in the second case but not in the first.

like image 538
dsinecos Avatar asked Mar 14 '19 12:03

dsinecos


People also ask

What is async stack traces?

Asynchronous stack traces allow you to inspect function calls beyond the current event loop. This is particularly useful because you can examine the scope of previously executed frames that are no longer on the event loop. This feature is currently an experiment and needs to be enabled.

Does async await block execution?

await only blocks the code execution within the async function. It only makes sure that the next line is executed when the promise resolves. So, if an asynchronous activity has already started, await will not have an effect on it.

Does await block the main thread?

The await operator doesn't block the thread that evaluates the async method. When the await operator suspends the enclosing async method, the control returns to the caller of the method.

Will async await block a thread node JS?

Though it creates a confusion, in reality async and await will not block the JavaScript main thread. Like mentioned above they are just syntactic sugars for promise chaining.


1 Answers

Because in the first code, everything up until the Error is on the same tick of the event loop.

Before an asynchronous callback, the one from setTimeout, can enter the call stack (The stack trace is built from it), the call stack must be empty.

So since the first code runs everything synchronous until the Error call, all those calls are in the call stack. But on the second approach, the Error is called after awaiting a setTimeout call. When the setTimeout is done. The event loop puts the callback back into the stack, for this, the call stack must be empty.

So now, you don't have functionTwo & functionThree on the call stack, that's why they don't appear.

The stack trace is the state of the stack when the error ocurred.

Here's a rough interpretation of what happens with the stack in both codes:

First Code

1) functionThree is pushed into the stack
2) functionTwo is pushed into the stack
3) functionOne is pushed into the stack
4) Error is thrown

Second Code

1) functionThree is pushed into the stack
2) functionTwo is pushed into the stack
3) functionOne is pushed into the stack
4) awat ...setTimeout is called
5) All 3 functions return a Promise
6) The stack is empty

... setTimeout ends
Next tick of the event loop

1) setTimeout callback is called
2) Error is thrown

I recommend watching this video to understand how all of this works:

What the heck is the event loop anyway by Philip Roberts


On Node 14+ you can use --async-stack-traces flag to have an improved stack trace when working with asynchronous code. There are some limitations like only working with async/await and not promises.

You can read a little bit more about this at https://v8.dev/blog/fast-async

like image 137
Marcos Casagrande Avatar answered Oct 10 '22 19:10

Marcos Casagrande