Stacktrace incomplete when throwing from async catch - javascript

Why is there no async stacktrace when rethrowing an asynchronous exception? With node 12+, the exception when running the following code:
async function crash() {
try {
await (async () => {throw new Error('dead');})();
} catch (e) {
throw new Error('rethrow');
}
}
async function foo() {
await new Promise(resolve => setTimeout(() => resolve(), 1));
await crash();
}
async function entrypoint() {
try {
await foo();
} catch(e) {
console.log(e.stack);
}
}
entrypoint();
is woefully incomplete:
Error: rethrow
at crash (/async-stackt/crash.js:6:15)
I found a workaround by defining the exception in the beginning of crash(), which yields a much nicer:
Error: rethrow
at crash (/workaround.js:2:17)
at foo (/workaround.js:12:11)
at async entrypoint (/workaround.js:17:9)
This is not optimal, since the error has to be constructed in advance whether it is needed or not, and the stacktrace is somewhat imprecise.
Why is the stack trace incomplete when throwing an error from an async catch block? Is there any workaround or change to the code to get a complete stack trace in the first place?

That stack trace is showing you what's ON the stack at that time. When you make an asynchronous call such as setTimeout(), it runs the setTimeout() which registers a timer that will fire some time in the future and then it continues execution. Since you're using await here, it pauses the execution of foo(), but it continues execution of the code after where foo() was called. Since that's also an await, it continues executing the code that called entrypoint(). After that finishes, the stack is entirely empty.
Then, sometime later, your timer fires and its callback gets called with a completely clean stack. In your case, the setTimeout() callback just calls resolve() which then triggers a promise to schedule its resolve handlers to run on the next event loop tick. That returns back to the system and the stack frame is again empty. On that next tick of the event loop, the promise resolve handlers are called and that satisfies the await on that promise which is inside a function context. When that await is satisfied, the rest of that function starts to execute.
When that function gets to the end of its execution, the interpreter knows that this was a suspended function context. There is no return from the function to happen because that already happened earlier. Instead, since this is an async function, the end of the function execution resolves the promise that this async function returns. Resolving that promise then schedules its resolve handlers to be called on the next tick of the event loop and then it returns control back to the system. The stack frame is again empty. On that next tick of the event loop, it calls the resolve handlers which satisifies the await in the await foo() statement and the entrypoint() function can continue to run, picking up where it was last suspended.
So, the key here is that when the timer goes off, execution goes from foo back to entrypoint, not via a stack and a return statement (that function already returned awhile ago), but via promises getting resolved. So, at the time the timer goes off and you then call crash(), the stack is indeed empty except for the function call to crash() itself.
This concept of an empty stack when promises are resolved goes to the heart of how an async function actually works so it's important to understand that. You have to remember that it pauses the internal execution of the function containing the await, but as soon as it hits the first await, it immediately causes the function to return a promise and the the callers continue further execution. The caller is not paused unless they also so an await in which case the callers of the caller continuing executing. At some point, somebody gets to continue executing and eventually, it returns control back to the system with a now-empty stack.
The timer event (or some other promise triggering event) then gets called with a completely empty stack frame with no remnants from the original call sequence.
Unfortunately, the only work-around I'm aware of now is to do what you found - create the Error object earlier when the original stack is still alive. If I remember correctly, there is a discussion about adding some features to the Javascript language to make asynchronous tracing easier. I don't remember the details of the proposal, but perhaps by remembering what the stack frame was when the function was originally called since what it is after the promise is resolved/rejected and when the Error object is created is no longer very useful.
In case anyone was unfamiliar with how async functions work and how they suspend their own execution upon the first await, but then return early, here's a little demo:
function delay(t) {
return new Promise(resolve => {
setTimeout(resolve, t);
});
}
async function stepA() {
console.log("5");
await stepB();
console.log("6");
}
async function stepB() {
console.log("3");
await delay(50);
console.log("4");
}
console.log("1");
stepA();
console.log("2");
This generates the following output. If you follow this execution path step-by-step, you will see how each await causes an early return from that function and can then see how the stack frame will be empty once the promise that is being awaited gets resolved. This is the output generated:
1
5
3
2
4
6
It's clear why 1 is first as it's the first thing to execute.
Then, it should be clear why 5 comes next when stepA() is first called.
Then, stepA calls stepB() so as it begins to execute, that's why we see 3 next.
Then, stepB calls await delay(50). That executes delay(50) which starts a timer and then immediately returns a promise that is hooked to that timer. It then hits the await and it stops execution of stepB.
When stepB hit that await, it causes stepB at that point to return a promise that comes from the function being async. That promise will be hooked to stepB execution eventually (in the future) getting a chance to finish all of its execution. For now the execution of stepB is suspended.
When stepB returns its promise, that goes back to where stepA executed await stepB();. Now that stepB() has returned (an unfulfilled promise), then stepA hits its await on that unfulfilled promise. That suspends the execution of stepA and it returns a promise at that point.
So, now that the original function call to stepA() has returned (an unfulfilled promise) and there is no await on that function call, that top level code after that function call continues to execute and we see the console output the 2.
That console.log("2") is that last statement to execute here so control is returned back to the interpreter. At this point, the stack frame is completely empty.
Then, sometime later, the timer fires. That inserts an event in the JS event queue. When the JS interpreter is free, it picks up that event and calls the timer callback associated with that event. This does only one thing (call resolve() on a promise) and then returns. Calling resolve on that promise schedules that promise to trigger it's .then() handlers on the next tick of the event loop. When that happens, the await on the line of code await delay(50); gets satisfied and execution of that function resumes. We then see the 4 in the console as the last line of stepB executes.
After the console.log("4"); executes, stepB has now finished executing and it can resolve it's async promise (the one returned by it earlier). Resolving that promise tells it to schedule its .then() handlers for the next tick of the event loop. Control goes back to the JS interpreter.
On the next tick of the event loop, the .then() handlers notify the await in the await stepB(); that the promise has now been resolved and execution of stepA continues and now we see 6 in the console. That is the last line of stepA to execute to it can resolve its async promise and return control back to the system.
As it turns out, there is nobody listening to the async promise that the call to stepA() returned so there is no further execution.

I ran into the same issue in Node 12 and found out that it was fixed in a later version of V8: commit.
Gotta wait for Node 14, I guess...
The bug had to do with the stack trace not being tracked in catch blocks. So one workaround is to use the Promise's .catch function instead:
async function crash() {
await (async () => {throw new Error('dead');})()
.catch(e => { throw new Error('rethrow'); });
}

Related

How working this code step by step in the event loop?

I can’t figure out how the code below works, my example of work contradicts the execution of the event loop.
async function main() {
for (let i = 0; i < 10; i++) {
await new Promise(res => setTimeout(res, 1000));
console.log("time + i");
}
}
main();
My example of how this code works:
function main added to Call stack.
call the loop for.
call function Promise which return setTimeout.
setTimeout added to Macrotask.
function main add to Microtask (await resolve Promise).
Some remarks about the steps you listed:
call function Promise which return setTimeout.
Promise is called as a function, but more specifically as a constructor
It doesn't return setTimeout, but ... a promise.
setTimeout added to Macrotask.
setTimeout is not added to a queue. setTimeout is executed, with res and a delay as argument. It returns immediately. The host has registered the callback and timeout using non-JavaScript technology. There is nothing about this in the queues yet.
function main add to Microtask
More precisely, the current execution context of main is saved. Once the awaited promise has resolved, only then will a job be put in the microtask queue with the purpose to resume the suspended execution of main.
Detailed steps
Here are the steps 3, 4 and 5 in more detail:
The Promise constructor is called, which immediately calls the callback given as argument.
setTimeout is called, which registers its callback argument res for later execution.
The setTimeout call immediately returns. Its return value is a unique timer ID, but that value is ignored by the Promise constructor
The Promise constructor returns a promise that is in a pending state.
The await saves the current execution context of main, and makes the main function return with a pending promise.
This is the end of this synchronous execution cycle, and JavaScript now monitors the job queues.
In a following step we have this:
The setTimeout implementation (non-JavaScript) sees that the 1000 delay has passed and places a job in a (macrotask) job queue.
The JavaScript engine gets the job from that queue, and executes it. In this case it means it executes res (starting from an empty call stack).
The call of res will fulfill the promise that was created with new Promise. This creates a job in a (microtaks) job queue to restore the execution context of main.
res returns and the callstack is empty again.
And then, as part of the same "task":
JavaScript reads the microtask queue and executes that job: the main execution context is restored (put on the callstack), with variable i equal to 0 and execution continuing with console.log
The loop makes its second iteration, and the operand of await is evaluated again, calling again the Promise constructor (see the steps at the top). The difference here is that when await makes main return (again) it is returning from a "resume" call that was made from a job, not from JavaScript code. This will be the case for all next executions of await.
And so it continues for all iterations of the (asynchronous) loop. When the loop has completed:
The resumed main function ends with an implicit return undefined. This resolves the promise that main had returned when it had executed await for the first time -- when it returned to the top-level script where main(); had been called.
As there is no code that is awaiting that promise to be resolved, nothing more happens.

Asynchronous code executing sequentially after wait instead of showing console output immediately

Here is my sample code:
async function counter() {
console.log('1');
let next = new Promise((resolve) => {setTimeout(() => resolve(3), 5000)});
let three = await next;
console.log('2');
console.log(three)
}
counter();
The accepted answer on this question says that
When you execute something synchronously, you wait for it to finish
before moving on to another task. When you execute something
asynchronously, you can move on to another task before it finishes.
What I understand from this is that 2 should be logged to the console immediately since the function is asynchronous and it shouldn't wait for another task (the promise) to finish (resolve).
However, what I see is that both 2 and 3 are logged together in the console after a wait of 5 seconds. Isn't that what a synchronous function would do based on the above text from the accepted answer?
Thanks.
The await expression causes async function execution to pause until a Promise is settled (that is, fulfilled or rejected), and to resume execution of the async function after fulfillment. When resumed, the value of the await expression is that of the fulfilled Promise.
You can read more about Await Here
And you can read more about this acccepted answer here
What happens basically is if you used await inside your async function, it causes to wait for it until promise is resolved. but outside of that function will still run in parallel to this function.

event emitter emit in sequence or in parallel, and behaviour when they are async

Consider the following code:
import events from 'events';
const eventEmitter = new events.EventEmitter();
eventEmitter.on('flush', (arg)=>{
let i=0;
while(i<=100000){
console.log(i, arg);
i++;
}
})
setTimeout(()=>{
eventEmitter.emit('flush', `Fourth event`);
}, 5000);
setTimeout(()=>{
eventEmitter.emit('flush', `Third event`);
}, 4000);
setTimeout(()=>{
eventEmitter.emit('flush', `First event`);
}, 2000);
setTimeout(()=>{
eventEmitter.emit('flush', `Second event`);
}, 3000);
Output1:
1 First event
2 First event
3 First event
.
.
.
1 Second event
2 Second event
3 Second event
.
.
.
1 Third event
2 Third event
3 Third event
.
.
.
1 Fourth event
2 Fourth event
3 Fourth event
What I wanted to know was, does the event that is emitted get completed first then only the second event get emitted? Or can I expect something like this:
Output2:
1 First event
1 Second event
2 Third event
3 Fourth event
3 Third event
.
.
.
What if I wrote the emitter.on function like this:
eventEmitter.on('flush', (arg)=>{
const mFunc = async ()=>{
let i=0;
while(i<=100000){
console.log(i, arg);
i++;
}
}
mFunc();
})
I was expecting output like Output2, but, instead I got something similar to Output1. Could someone please explain me this behaviour?
Also, consider this case:
eventEmitter.on('flush', (arg)=>{
const mFunc = async ()=>{
let i=0;
while(i<=100000){
console.log(i, arg);
i++;
await updateValueInDatabase(i);
}
}
mFunc();
})
Now, what would be the behaviour of the function?
Coalescing my comments into an answer and adding more commentary and explanation...
Emitting an event from an EventEmitter is 100% synchronous code. It does not go through the event loop. So, once you call .emit(), those event handlers will run until they are done before any other system events (e.g. your timers) can run.
Calling an async function is NO different from the calling point of view at all than calling a non-async function. It doesn't change anything. Now, inside an async function, you could use await and that would cause it to suspend execution and immediately return a promise, but without await, making a function async just makes it return a promise, but doesn't change anything else. So, calling the async version of mFunc() is exactly the same as if it wasn't async (except it returns a promise which you don't use). Doesn't changing sequencing of events at all.
Since you apparently thought it would change things, I'd really suggest reading a bunch more about what an async function is because your perception of what it does is apparently different than what it actually does. All async functions return a promise and catch internal exceptions which they turn into a rejected promise. If you don't use await, they just run synchronously like everything else and then return a promise.
Calling an async function returns ONLY when you hit an await or when the function finishes executing and hits a return or the implicit return at the end of the function. If there's no await, it just runs like a normal function and then returns a promise (which you aren't doing anything with). As I said above, you really need to read more about what async functions actually are. Your current understanding is apparently incorrect.
Here are some summary characteristics of an async function:
They always return a promise.
That promise is resolved with whatever value the function eventually returns with a return statement or resolved with undefined if no return statement.
They run synchronously until they either hit a return statement or an await.
At the point they hit an await, they immediately return a promise and further execution of the function is suspended until the promise that await is on is resolved.
When they suspend and return a promise, the caller receives that promise and keeps executing. The caller is not suspended unless the caller also does an await on that promise.
They also catch any synchronous exceptions or other exceptions that aren't themselves in an asynchronous callback and turn those exceptions into a rejected promise so the promise that the async function returns will be rejected with the exception as the reason.
So, if there is no await in an async function, they just run synchronously and return a promise.
In your last version of code, if you end up making your event emitter handler actually be asynchronous and actually await long enough for other things to get some cycles, then you can create some competition between timers and promises waiting to notify their listeners. The promises waiting to notify their listeners will get to run before the timers. That makes your situation of mixing those two types very complicated and very dependent upon timings. A non-ending sequence of promises waiting to notify their listeners can make timers wait until there are no more promises waiting to notify. But, if there's a moment with no promises waiting to notify, then your next timer will fire and will kick off it's own set of promise-driven operations and then all the promise-driven operations will likely interleave.
Also, emitter.emit() is not promise-aware. It doesn't pay any attention to the return value from the callbacks that are listening for the emit. So, it doesn't make any difference at all to emitter.emit() whether the listeners are async or not. As soon as they return (whether they returned a promise or not), it just goes right on to whatever it was going to do next. Promises only influence code flow if the recipient uses them with await or .then() and .catch(). In this case, the recipient does nothing with the return value so emitter.emit() just goes right onto its next order of business and executes that.
okay, so, if I have a bunch of async function arrays [async1, async2, async3, ....], and they all have await statements internally, what would be the best way to execute them in sequential order? i.e. one after other in order of their index?
Well, if you had an array of async functions that properly resolve their promise when they are actually done with their work, you can execute them sequentially by just looping through the array with an await.
async function someFunc() {
const jobs = [asyncFn1, asyncFn2, asyncFn3];
for (let job of jobs) {
let result = await job(...);
}
}
Internally, the EvenEmitter hold an array of listeners for each event type. When you emit an event type, the EvenEmitter just goes through it's array of listeners and execute it. Therefore, the listeners are executed in the order it was added by addListener or on method.
To answer your questions there are two parts: a) it executes in the order you added the listener and b) it depends if the listeners are async or not. If your listeners are synchronous, then you can expect that behavior. If your listeners are async, you can't expect that. Even if your execute your async synchronously, they will not necessarily resolve at the same time because it's the nature of being async.

Why do I need to use async/await twice to make this Non-blocking? [duplicate]

This question already has answers here:
Will async/await block a thread node.js
(6 answers)
Closed 3 years ago.
I have two functions, the first is the main function which has switch statement calling the second which is async. If my second function is async isn't that non-blocking? Would I still have to make the first one async in order for it to be non-blocking, if so why?
Example
exports.funcOne = async (theParam) => { // async ??
switch (theParam) {
case 'hey':
return await funcTwo()
default:
...
}
}
const funcTwo = async () => {
await axios.get...
}
Second function can just return the promise :
exports.funcOne = async (theParam) => { // async ??
switch (theParam) {
case 'hey':
const myData = await funcTwo();
console.log("response from request : ", myData);
//do something with data
default:
...
}
}
const funcTwo = () => {
//axios.get is a promise, so is the return type for funcTwo
return axios.get...
}
Calling an AsyncFunction returns a Promise. As Eric said you could return the Promise in the funcTwo and it doesnt need to be an AsyncFunction because it is secuential and it is in the functionOne thread. So if you return the Promise in the functionTwo, the result of the Promise "axios.get..." will be resolved in "return await funcTwo()" in the functionOne.
In general, all functions that contain await must be async.
That rule might make more sense if we imagine it would not exist, and if you could wait for an asynchronous result in a synchronous function.
First of all, we need to know one important things about functions:
They have to run to completion, or in other words: A regular function runs till it's return and no other code can run at the same time.
If we could await inside of a non-async function that would mean that we would block execution until the awaited promise resolves, as no other code can run in the meantime. As the code that resolves the promise also cannot run, we created a deadlock.
async functions only run to completion till they reach an await. Therefore, when axios.get(...) gets called, a promise gets returned synchronously from it, funcTwo halts execution, returns a promise itself, therefore funcOne also halts execution (thats possible cause it's async). Then the engine can continue with other stuff, and somewhen when the underlying request is done, the promise resolves, funcTwo continues execution which resolves the promise it returned, funcOne continues execution and resolves the returned promise too.
If my second function is async isn't that non-blocking?
That really depends on your definition of non-blocking. In general, every code of JS that executes kind of "blocks the thread" (as only one function can execute at a time), but as most tasks are very small, you won't notice that blocking.
An asnyc function is also blocking, as long as it doesn't halt for a promise to resolve. Then it is in a non-blocking waiting state.
Asynchronous tasks (e.g. doing a network request) also aren't blocking, cause they are handled by the engine (offloaded to other threads or hardware, etc.).

Working of call stack when async/await is used

How does the Call Stack behave when async/await functions are used ?
function resolveAfter2Seconds() { // taken from https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function
return new Promise(resolve => {
setTimeout(() => {
resolve('resolved');
}, 2000);
});
}
const asyncFuntion=async()=>{
const result = await resolveAfter2Seconds();
console.info("asyncFuntion finish, result is: ", result);
}
const first = async()=>{
await asyncFuntion();
console.log('first completed');
debugger;
}
const second = ()=>{
console.log('second completed');
debugger;
}
function main(){
first();
second();
}
main();
In the above code, when the first breakpoint is encountered in second(), I could see that the call stack contained main() and second(). And during the second breakpoint in first(), the call stack contained main() and first().
What happened to first() during the first breakpoint. Where is it pushed ? Assuming that the asyncFunction() takes some time to complete.
Someone please help.
First off, when you get to the breakpoint you hit in second, first has already executed and is no longer on the stack.
When we go into first, we instantly hit an await asyncFunction(). This tells JavaScript to not block on the result of the call, but feel free to go looking for something else to do while we're waiting. What does Javascript do?
Well, first of all, it does call asyncFunction(). This returns a promise, and will have started some asynchronous process that will later resolve it. Now we can't continue with the next line of first (ie, the console.log('first completed')) because our await means we can't carry on until the promise was fulfilled, so we need to suspend execution here and go find something else to do with our free time.
So, we look up the stack. We're still in the first() call from main, and now we can just return a promise from that call, which we will resolve when the asynchronous execution completes. main ignores that promise return value, so we continue right with second(). Once we've executed second, we look back to whatever called that, and carry on with synchronous execution in the way everyone would expect.
Then, at some point in the future, our promise fulfills. Maybe it was waiting on an API call to return. Maybe it was waiting on the database to reply back to it. In our example, it was waiting for a 2s timeout. Whatever it was waiting for, it now is ready to be dealt with, and we can un-suspend the first call and continue executing there. It's not "called" from main - internally the function is picked back up, just like a callback, but crucially is called with a completely new stack, which will be destroyed once we're done calling the remainder of the function.
Given that we're in a new stack, and have long since left the 'main' stack frame, how do main and first end up on the stack again when we hit the breakpoint inside it?
For a long time, if you ran your code inside debuggers, the simple answer was that they wouldn't. You'd just get the function you were in, and the debugger would tell you it had been called from "asynchronous code", or something similar.
However, nowadays some debuggers can follow awaited code back to the promise that it is resolving (remember, await and async are mostly just syntactic sugar on top of promises). In other words, when your awaited code finishes, and the "promise" under the hood resolves, your debugger helpfully figures out what the stack "should" look like. What it shows doesn't actually bear much resemblance to how the engine ended up calling the function - after all, it was called out of the event loop. However, I think it's a helpful addition, enabling us all to keep the mental model of our code much simpler than what's actually going on!
Some further reading on how this works, which covers much more detail than I can here:
Zero-cost async stack traces
Asynchronous stack traces

Categories

Resources