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 await
ing 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