Skip to content
Advertisement

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.

Advertisement

Answer

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

Advertisement