Working of call stack when async/await is used - javascript

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

Related

Can I use setTimeout() on JavaScript as a parallel processed function

I know JS is single threaded. But I have a function which takes time for the calculation. I would like it to work paralleled, so this function would not freeze next statement. Calculation inside of function will take approximately 1-2 seconds.
I used to create it using promise, but it still freeze the next statement.
console.log("start");
new Promise((res, rej) => {
/* calculations */
}).then((res) => console.log(res));
console.log("end");
Then I used setTimeout function with time interval 0. LoL
console.log("start");
setTimeout(() => {
/* calculations */
console.log(res);
}, 0);
console.log("end");
Outputs:
start
end
"calculation result"
Both cases shows similar result, but using promise prevented to show console.log("end") before calculation finishes. Using setTimeout works as I wanted, and shows console.log("end") before calculation, so it was not freeze till calculation done.
I hope it was clear enough. Now for me using setTimeout is the best solution, but I would be happy to hear your ideas or any other method calculating concurrently without setTimeout.
The code you write under new Promise(() => {..code here..}) is not asynchronous. The is a very common misconception that everything under the Promise block would run asynchronously.
Instead, this JS API just let's get us a hook of some deferred task to be done once the promise is resolved. MDN
Promises are a comparatively new feature of the JavaScript language that allow you to defer further actions until after a previous action
has completed, or respond to its failure. This is useful for setting
up a sequence of async operations to work correctly.
new Promise(() => {
// whatever I write here is synchromous
// like console.log, function call, setTimeout()/fetch()/async web apis
// if there are some async tasks like fetch, setTimeout.
// they can be async by themselves but their invocation is still sync
//
})
setTimeout is not the correct option either. Code under setTimeout would run when the event stack is empty and once it enters, it would block the main thread again.
The right approach to this would be to use Web Workers.

What's the best way to run a promise synchronously when it's already resolved?

When a promise is resolved, it resumes code execution after any awaits in the next task.
So for instance:
console.log("start");
async function foo(){
await Promise.resolve();
console.log("foo");
}
foo();
console.log("end");
prints "start", "end", "foo". Even though the promise is already resolved, it still waits for any other code to execute first, and only after that it resumes and prints foo.
I would like to execute any awaits synchronously for resolved promises.
The only way I can think of to achieve this is to check if a promise has been fulfilled and wrap the await in an if statement so that it only waits if the promise is still pending. But figuring out if a promise is pending (and getting the resolved value) seems very tedious to do synchronously. Does anyone know if there's a better way?
More context
I have a game loop that needs to not run for more than a few milliseconds per frame. There is one specific function that I need to call in order to prepare some gltf assets. This function takes more than a second. So I'd like to devide this up into chunks, so it excecutes only part of this function every frame.
The easiest way to achieve this that I could think of was to make the function async and checking if a certain amount of time has passed. If more than a few milliseconds have passed, I will await waitForFrameRender(), which will essentially stop the execution until the next frame. However, this will add tons of awaits in the function (it has a bunch of loops), and most of them won't really have to wait for anything because the function hasn't been running for more than X amount of milliseconds yet.
Therefore it seemed to make more sense to skip these waits and run these parts synchronously.
In my current setup I have an if statement to check how much time has passed, and only await if it has actually been running for more than a few milliseconds. Which works to a certain degree, but the function that takes a second to execute also has nested functions that are async for the same reason. So the nested function calls still need an await.
I realize that this sounds like web workers are the solution. I haven't actually tried this yet but I feel like transferring the javascript object that the function returns will have too much overhead for this. And since it is ok for this function to not return immediately (even 20 seconds or so would be fine) simply making the function async seemed like the easiest way to do it.
If this is what you want move end into the promise. The other alternative would be to put end in its own promise
console.log("start");
async function foo(){
await Promise.resolve();
console.log("foo");
console.log("end");
}
foo();
or
console.log("start");
async function foo(){
await Promise.resolve();
console.log("foo");
}
foo();
await Promise.resolve();
console.log("end");
This is probably what you want. You need to await until foo() is done running.
async function run() {
console.log("start");
await foo();
console.log("end");
}
run();

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

Resolving of promise is done asynchronously even for Promise.resolve()?

console.log(1);
let p = Promise.resolve(2);
p.then(val => console.log(val));
console.log(3);
The above code prints 1 3 2, but since the promise resolving is done synchronously, shouldn't the callback also execute synchronously? Can anyone explain this behavior?
As per the mozilla developer networks docs:
[Promise] callbacks will never be called before the completion of the current run of the JavaScript event loop.
So your .then() callback is called after the current code has finished.
Javascript is single threaded (mostly). To allow JS to scale it relies heavily on asynchronous execution of code. So imagine making a phone call, you ask a question, the person on the other end of the line has to look up the answer, so you wait. All the time your waiting you're not doing anything. This is wasteful of your time.
Now imagine doing the same thing but this time you tell the person on the other end of the line to tell you when they've got the answer. You hang up and do something else. When the other person has finished they call you back to give you the answer to your question. This is a much more efficient use of your time.
In your code the Promise is you making the phone call. The then is the person you've called calling you back when they have the result and your (the person making the call that is) the Js thread.
So you do:
//1
console.log(1);
You then call someone (promise) and ask for them to do some work:
let p = Promise.resolve(2); //nothing printed here
They say they'll call you back:
.then(...) //nothing printed here
You do other work
//3
console.log(3);
They call you back:
//2
val => console.log(val)
1
3
2
but since the promise resolving is done synchronously
No it's not promises are resolved asynchronously
#daphtdazz makes a very good point on why it doesn't just print 2 straight away
console.log is immediate execution. So imagine the person you've just hung up on tries to call you back straight away. But you don't answer yet because you just need to finish this other task. Then you answer.
The code which is executed in .then callbacks of promise is asynchronous always.
The code inside the promise is not asynchronous.
console.log(1);
let p = new Promise((res, rej) => {
console.log('sync');
res(2);
});
p.then(val => console.log('async ' +val));
console.log(3);

Using "await" inside non-async function

I have an async function that runs by a setInterval somewhere in my code. This function updates some cache in regular intervals.
I also have a different, synchronous function which needs to retrieve values - preferably from the cache, yet if it's a cache-miss, then from the data origins
(I realize making IO operations in a synchronous manner is ill-advised, but lets assume this is required in this case).
My problem is I'd like the synchronous function to be able to wait for a value from the async one, but it's not possible to use the await keyword inside a non-async function:
function syncFunc(key) {
if (!(key in cache)) {
await updateCacheForKey([key]);
}
}
async function updateCacheForKey(keys) {
// updates cache for given keys
...
}
Now, this can be easily circumvented by extracting the logic inside updateCacheForKey into a new synchronous function, and calling this new function from both existing functions.
My question is why absolutely prevent this use case in the first place? My only guess is that it has to do with "idiot-proofing", since in most cases, waiting on an async function from a synchronous one is wrong. But am I wrong to think it has its valid use cases at times?
(I think this is possible in C# as well by using Task.Wait, though I might be confusing things here).
My problem is I'd like the synchronous function to be able to wait for a value from the async one...
They can't, because:
JavaScript works on the basis of a "job queue" processed by a thread, where jobs have run-to-completion semantics, and
JavaScript doesn't really have asynchronous functions — even async functions are, under the covers, synchronous functions that return promises (details below)
The job queue (event loop) is conceptually quite simple: When something needs to be done (the initial execution of a script, an event handler callback, etc.), that work is put in the job queue. The thread servicing that job queue picks up the next pending job, runs it to completion, and then goes back for the next one. (It's more complicated than that, of course, but that's sufficient for our purposes.) So when a function gets called, it's called as part of the processing of a job, and jobs are always processed to completion before the next job can run.
Running to completion means that if the job called a function, that function has to return before the job is done. Jobs don't get suspended in the middle while the thread runs off to do something else. This makes code dramatically simpler to write correctly and reason about than if jobs could get suspended in the middle while something else happens. (Again it's more complicated than that, but again that's sufficient for our purposes here.)
So far so good. What's this about not really having asynchronous functions?!
Although we talk about "synchronous" vs. "asynchronous" functions, and even have an async keyword we can apply to functions, a function call is always synchronous in JavaScript. An async function is a function that synchronously returns a promise that the function's logic fulfills or rejects later, queuing callbacks the environment will call later.
Let's assume updateCacheForKey looks something like this:
async function updateCacheForKey(key) {
const value = await fetch(/*...*/);
cache[key] = value;
return value;
}
What that's really doing, under the covers, is (very roughly, not literally) this:
function updateCacheForKey(key) {
return fetch(/*...*/).then(result => {
const value = result;
cache[key] = value;
return value;
});
}
(I go into more detail on this in Chapter 9 of my recent book, JavaScript: The New Toys.)
It asks the browser to start the process of fetching the data, and registers a callback with it (via then) for the browser to call when the data comes back, and then it exits, returning the promise from then. The data isn't fetched yet, but updateCacheForKey is done. It has returned. It did its work synchronously.
Later, when the fetch completes, the browser queues a job to call that promise callback; when that job is picked up from the queue, the callback gets called, and its return value is used to resolve the promise then returned.
My question is why absolutely prevent this use case in the first place?
Let's see what that would look like:
The thread picks up a job and that job involves calling syncFunc, which calls updateCacheForKey. updateCacheForKey asks the browser to fetch the resource and returns its promise. Through the magic of this non-async await, we synchronously wait for that promise to be resolved, holding up the job.
At some point, the browser's network code finishes retrieving the resource and queues a job to call the promise callback we registered in updateCacheForKey.
Nothing happens, ever again. :-)
...because jobs have run-to-completion semantics, and the thread isn't allowed to pick up the next job until it completes the previous one. The thread isn't allowed to suspend the job that called syncFunc in the middle so it can go process the job that would resolve the promise.
That seems arbitrary, but again, the reason for it is that it makes it dramatically easier to write correct code and reason about what the code is doing.
But it does mean that a "synchronous" function can't wait for an "asynchronous" function to complete.
There's a lot of hand-waving of details and such above. If you want to get into the nitty-gritty of it, you can delve into the spec. Pack lots of provisions and warm clothes, you'll be some time. :-)
Jobs and Job Queues
Execution Contexts
Realms and Agents
You can call an async function from within a non-async function via an Immediately Invoked Function Expression (IIFE):
(async () => await updateCacheForKey([key]))();
And as applied to your example:
function syncFunc(key) {
if (!(key in cache)) {
(async () => await updateCacheForKey([key]))();
}
}
async function updateCacheForKey(keys) {
// updates cache for given keys
...
}
This shows how a function can be both sync and async, and how the Immediately Invoked Function Expression idiom is only immediate if the path through the function being called does synchronous things.
function test() {
console.log('Test before');
(async () => await print(0.3))();
console.log('Test between');
(async () => await print(0.7))();
console.log('Test after');
}
async function print(v) {
if(v<0.5)await sleep(5000);
else console.log('No sleep')
console.log(`Printing ${v}`);
}
function sleep(ms : number) {
return new Promise(resolve => setTimeout(resolve, ms));
}
test();
(Based off of Ayyappa's code in a comment to another answer.)
The console.log looks like this:
16:53:00.804 Test before
16:53:00.804 Test between
16:53:00.804 No sleep
16:53:00.805 Printing 0.7
16:53:00.805 Test after
16:53:05.805 Printing 0.3
If you change the 0.7 to 0.4 everything runs async:
17:05:14.185 Test before
17:05:14.186 Test between
17:05:14.186 Test after
17:05:19.186 Printing 0.3
17:05:19.187 Printing 0.4
And if you change both numbers to be over 0.5, everything runs sync, and no promises get created at all:
17:06:56.504 Test before
17:06:56.504 No sleep
17:06:56.505 Printing 0.6
17:06:56.505 Test between
17:06:56.505 No sleep
17:06:56.505 Printing 0.7
17:06:56.505 Test after
This does suggest an answer to the original question, though. You could have a function like this (disclaimer: untested nodeJS code):
const cache = {}
async getData(key, forceSync){
if(cache.hasOwnProperty(key))return cache[key] //Runs sync
if(forceSync){ //Runs sync
const value = fs.readFileSync(`${key}.txt`)
cache[key] = value
return value
}
//If we reach here, the code will run async
const value = await fsPromises.readFile(`${key}.txt`)
cache[key] = value
return value
}
Now, this can be easily circumvented by extracting the logic inside updateCacheForKey into a new synchronous function, and calling this new function from both existing functions.
T.J. Crowder explains the semantics of async functions in JavaScript perfectly. But in my opinion the paragraph above deserves more discussion. Depending on what updateCacheForKey does, it may not be possible to extract its logic into a synchronous function because, in JavaScript, some things can only be done asynchronously. For example there is no way to perform a network request and wait for its response synchronously. If updateCacheForKey relies on a server response, it can't be turned into a synchronous function.
It was true even before the advent of asynchronous functions and promises: XMLHttpRequest, for instance, gets a callback and calls it when the response is ready. There's no way of obtaining a response synchronously. Promises are just an abstraction layer on callbacks and asynchronous functions are just an abstraction layer on promises.
Now this could have been done differently. And it is in some environments:
In PHP, pretty much everything is synchronous. You send a request with curl and your script blocks until it gets a response.
Node.js has synchronous versions of its file system calls (readFileSync, writeFileSync etc.) which block until the operation completes.
Even plain old browser JavaScript has alert and friends (confirm, prompt) which block until the user dismisses the modal dialog.
This demonstrates that the designers of the JavaScript language could have opted for synchronous versions of XMLHttpRequest, fetch etc. Why didn't they?
[W]hy absolutely prevent this use case in the first place?
This is a design decision.
alert, for instance, prevents the user from interacting with the rest of the page because JavaScript is single threaded and the one and only thread of execution is blocked until the alert call completes. Therefore there's no way to execute event handlers, which means no way to become interactive. If there was a syncFetch function, it would block the user from doing anything until the network request completes, which can potentially take minutes, even hours or days.
This is clearly against the nature of the interactive environment we call the "web". alert was a mistake in retrospect and it should not be used except under very few circumstances.
The only alternative would be to allow multithreading in JavaScript which is notoriously difficult to write correct programs with. Are you having trouble wrapping your head around asynchronous functions? Try semaphores!
It is possible to add a good old .then() to the async function and it will work.
Should consider though instead of doing that, changing your current regular function to async one, and all the way up the call stack until returned promise is not needed, i.e. there's no work to be done with the value returned from async function. In which case it actually CAN be called from a synchronous one.

Categories

Resources