Does a async function gets added to the callstack? - javascript

If i declare a function like below:
function a(){
//do something
}
And if i execute it with a() it gets putted onto the top of the callstack and gets popped of when its finished. While the function is in the callstack the main thread is blocked and cant do other things until the callstack is empty.
But what happens exactly with an async function?
if i do:
async function b() {
//do something
}
The function returns an promise and does not block the main thread. Does this mean this function gets passed to the web API instead of the callstack? and then after its done it gets passed to the callback que and then gets passed to the callstack and so that we can execute the callback function?
b.then(function() {
//do something after finish
})

Yes, async functions will be on the call stack during their execution just like normal functions. You can consider the desugaring of the await keyword:
function log(x) {
console.log("at "+x);
}
function delay(t) {
return new Promise(resolve => setTimeout(resolve, t));
}
async function example() {
log(1);
await delay(50);
log(2);
return 3;
}
function example2() {
log(1);
return delay(50).then(function cont() {
log(2);
return 3;
})
}
An example() call works exactly like an example2() call. The called function gets pushed on the stack, calls log(1) which gets pushed on the stack and runs, when that returns (gets popped from the stack) it calls delay(50) which gets put on the stack and runs, and so on. Now, when the await operator is evaluated, the async function will take the operand promise, attach callbacks using a .then() call, and then return from the example() call a promise for the eventual completion of the function body. So when it reaches the await keyword, it gets popped from the stack, and the caller may continue running synchronous code (that will typically involve doing something with the returned promise).
Then, when the promise is resolved (in the above example, when the timeout is hit), jobs are scheduled to run the attached then callbacks. They might have to wait for the event loop to become idle (the callstack to become empty), and then when the promise job starts the code is put on the callstack again. In example2, this is the cont function that will get called, in the async function it will be continuation of the example body, where it left off at the await. In both cases, it calls log(2) which gets pushed on the stack and runs, and when that returns (gets popped from the stack) the return 3 statement is executed which resolves the promise and pops the execution context from the stack, leaving it empty.

it does go onto the call stack, when its time for the async function to run it gets moved off of the call stack until the function is either fulfilled.or rejected. at this point it get moved into the callback queue while all other synchronous functions can continue to execute off of the callstack, then when the callstack is empty, the event loop looks in the callback queue for anything else that needs to be executed. if there is anything in the callback queue the event loop pushes the callback into the call stack to execute.

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.

Why does the control goes back to the caller function after the execution of the first await statement in an async function?

Here's the code:
function B() {
return 'B';
}
async function test(b) {
await console.log('Z')
await console.log(b())
console.log('X')
await console.log("hihihi")
}
console.log('A');
test(B);
console.log('C');
console.log('P');
This output's to A Z C P B X hihihi
Question is:
Why does the control goes to the caller function(that called the async function) after executing the first await statement? How's it useful?
What's to be done to force execute all await statements one after the other somehow preventing it from going to the caller function?
Right now, your single node thread sees an order of execution that looks like this
console.log('A') // > A
test(B) // oh okay, looks like an async function call. i'll start this off and move on
console.log('C'); // > C
console.log('P'); // > P
When test(B) is called, another "thread" of sorts handles the execution. This is all because test is an async function, so every operation inside that function is handled a bit differently than a regular synchronous function.
If you want them all to appear in order, you can use another async function and await test(B)
function B(){
return 'B';
}
async function test(b){
await console.log('Z')
await console.log(b())
console.log('X')
await console.log("hihihi")
}
async function main() {
console.log('A');
await test(B);
console.log('C');
console.log('P');
}
main()
All of this is pretty useful because of a nodejs thing called non-blocking io. Here's a question that could shed some light on that.
Here is node's very own explanation of the event loop, which is a key concept for understanding non-blocking io
When a promise is done, any further execution is going into a queue there is executed after the code there is running at the moment,
first, console.log('A') is called as normal
then console.log('Z') is called as normal, but javascript is exception a promise, but it is fine for it to take a normal statement, but any code after the call stack is been emptying
then c and p is logged
now the callstack is empty, and now its time for evaluating the microtask queue
b is called. and another microtask is carried out
x is logged normal
hi hi hi is logged, and another microtask is started, but then the function exits
I really do recommend watching "the event loop" which goes into details with this functionality https://www.youtube.com/watch?v=cCOL7MC4Pl0 done by Jake which also have an article on it https://jakearchibald.com/2015/tasks-microtasks-queues-and-schedules/
everthing is called on the same thread, see richytong's code block for your 2nd question

Stacktrace incomplete when throwing from async catch

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'); });
}

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

Callback function in node.js

I am new to node.js and relatively new to javascript. I have understood how callbacks works and wanted to try out a function myself. Here is my code:
MyScript.js:
var calledfunction = function()
{
console.log("This is a called function");
for(i=0;i<1090660;i++)
{
console.log(i);
}
console.log('done');
};
var sayHello = require('./sayhello.js');
objhello = new sayHello();
objhello.setupSuite(1,calledfunction);
console.log('Next statement;');
sayhello.js
var _ = require('underscore');
module.exports = exports = CLITEST;
function CLITEST(param1,param2)
{
}
_.extend(CLITEST.prototype, {
setupSuite: function (here,callback) {
console.log(here);
console.log('This is a callback function');
callback();
}
})
The above program is run by executing > node Myscript.js
My question is : the for loop consumes 50 secs to execute and print all the numbers in the console and then only executes the line "Next statement" which is outside the callback function .
Why is this happening? because I read theories saying that the immediate statements will be executed without having to wait for the function to get executed.
The ideal output should have been : print " Next statement" and then print the contents of the for loop
but in the above case it is vice versa ?
This is not a real callback, but rather a simple function call. Function calls are obviously synchronous, as the following statements may rely on their return values.
In order to may the callback async you can use: setTimeout or setImmediate, depending on the actual use case.
See also: Are all Node.js callback functions asynchronous?
As pointed out by one of the commenters, your code is executed in a synchronous fashion. The function calls are executed one after the other, thus no magic is happening and the console.log('Next statement;'); call is executed after the execution of the callback. If you were in a situation in which you had to call a function which executed an asynchronous call (i.e., an AJAX call with a callback) then yes, you would expect the subsequent console.log to be executed right after the asynchronous call.
In other words, in your case the code represents a simple function call, while an asynchronous call would offload the computation of the callback somewhere else, thus the execution of the code where the callback function was called keeps going by printing the statement and won't wait for the execution of the callback.

Categories

Resources