If I have a try/catch
in one of my calls and it causes the stack to "reset" at that point. I have 2 questions because of this -
1) Why does this happen? I imagine it's something with how the v8 engine works but it would be interesting to know why.
2) Is there a good solution to use async/await
and still keep the entire stack trace? Right now I am putting a try/catch
all the way down the function call chain and rolling an error into a new error all the way back out (using VError).
The following code gives the stack trace I would expect
async function one() {
throw new Error("blah");
}
async function two() {
await one();
}
async function three() {
await two();
}
async function four() {
try {
await three();
} catch (e) {
console.log(e);
}
}
four();
stack trace
Error: blah
at one (/dev/async-stack/correct-stack.js:2:9)
at two (/dev/async-stack/correct-stack.js:6:9)
at three (/dev/async-stack/correct-stack.js:10:9)
at four (/dev/async-stack/correct-stack.js:15:11)
at Object.<anonymous> (/dev/async-stack/correct-stack.js:21:1)
at Module._compile (module.js:652:30)
at Object.Module._extensions..js (module.js:663:10)
at Module.load (module.js:565:32)
at tryModuleLoad (module.js:505:12)
at Function.Module._load (module.js:497:3)
Putting in a try/catch
in the middle causes a stack trace to start where the last try/catch
was.
async function one() {
throw new Error("blah");
}
async function breaker() {
return true;
}
async function stack() {
try {
await breaker();
} catch (error) {
throw error;
}
}
async function two() {
await stack(); // <-- this call
await one();
}
async function three() {
await two();
}
async function four() {
try {
await three();
} catch (e) {
console.log(e);
}
}
four();
stack trace
Error: blah
at one (/dev/async-stack/broken-stack.js:2:9)
at two (/dev/async-stack/broken-stack.js:19:9)
at <anonymous>
at process._tickCallback (internal/process/next_tick.js:188:7)
at Function.Module.runMain (module.js:695:11)
at startup (bootstrap_node.js:188:16)
at bootstrap_node.js:609:3
That's how async
/await
syntax desugars. For your first snippet, it's like1
function one() {
return Promise.reject(new Error("blah"));
}
function two() {
return one().then(() => {});
}
function three() {
return two().then(() => {});
}
function four() {
return three().then(() => {}).catch(e => { console.log(e); });
}
four();
while your second snippet works like1
function one() {
return Promise.reject(new Error("blah"));
}
function breaker() {
return Promise.resolve(true);
}
function stack() {
return breaker().then(() => {}).catch(error => { throw error; });
}
function two() {
return stack().then(() => {
// ^^^^^^^ this anonymous function
return one().then(() => {});
})
}
function three() {
return two().then(() => {});
}
function four() {
return three().then(() => {}).catch(e => { console.log(e); });
}
four();
As you can see, one()
is indeed called from inside an anonymous then
callback. It actually doesn't have anything to do with try
/catch
as your title suggests, but rather that any await
precedes the one()
call.
1: Ignoring details such as Promise
constructor calls, which probably use a deferred pattern internally so that they don't show up in the stack traces.
A more pedantic way would be to write function() { var _resolve, _reject,
_promise = new Promise((res, rej) => { _resolve = res; _reject = rej; }); try { /* function body */ _resolve(_return_value); } catch(e) { _reject(e); } return _promise; }