How can await promise resolve if resolve(...) is inside setTimeout? - javascript

What is the order of execution for code below?
If await makes us wait for "promise" to resolve, but resolve(...) is inside a setTimeout, and a setTimeout only gets executed after the stack is empty... How are we not frozen on await? Are there not still things on the stack when we are "await"ing? Is there some exception to an await-y setTimeout , or am I not understanding the stack?
async function f() {
let promise = new Promise((resolve, reject) => {
setTimeout(() => resolve("done!"), 1000)
});
let result = await promise; // wait until the promise resolves (*)
alert(result); // "done!"
}
f();

await is basically a substitute for .then - it says "do the rest of this stuff only once the Promise has resolved". It does not freeze the interpreter at the point of the await - it only pauses execution flow for the current function until the Promise resolves.
So
// wait until the promise resolves (*)
might be more accurately read as
// pause execution of this function until the promise resolves (*)
Are there not still things on the stack when we are "await"ing?
Yes, f() returns a Promise (from the async function) synchronously while the await promise is being waited on to resolve. Then the script doesn't do anything else, so the call stack lies completely empty until the setTimeout macrotask runs a second later, adding a short task that calls resolve, which queues a microtask that shortly results in done being logged.
There are a number of times during your code in which there is no currently active task.

Related

Why is the execution order not sequentyla in promises and await functions in JavaScript?

I have a code:
async function hh(){
const promise1 = new Promise((resolve,reject) => {
resolve("First promise");
});
const promise2 = new Promise((resolve,reject) => {
resolve("Second promise");
});
promise2.then((a)=>console.log(a));
console.log(await promise1);
console.log("sync call");
}
hh();
result of this code is:
Second promise
First promise
sync call
My first question why sync call is not on the first place?
Then I just deleted from the code console.log(await promise1);
So the code looks like this:
async function hh(){
const promise1 = new Promise((resolve,reject) => {
resolve("First promise");
});
const promise2 = new Promise((resolve,reject) => {
resolve("Second promise");
});
promise2.then((a)=>console.log(a));
console.log("sync call");
}
hh();
End result now is:
sync call
Second promise
So now sync call is on the first place, just beacuse I deleted the second call, why?
This is what happens in that code:
First version
promise1 and promise2 are promises in status fulfilled -- you could have used the Promise.resolve function instead of new Promise.
promise2.then() registers a callback to be executed when the promise is fulfilled, which it is, so the callback is enqueued as a job in the promise job queue.
await promise1 suspends the hh function and registers hh to be resumed when the promise is fulfilled, which it is, so the resume of hh is enqueued as a job in the promise job queue (which now has two entries).
hh() returns a promise (that is ignored) and the JS call stack becomes empty. Now the event loop will extract the first job and execute it. This outputs "Second promise".
Then the call stack is empty again. The event loop will extract the last job and execute it. This resumes the hh execution context, which executes the console.log and outputs "First promise". It then continues with "sync call". Note that these two console.log are executed in one synchronous sequence, so there is absolutely no way that they could have their outputs in a different relative order.
Second version
Here the await is missing, so there is no suspension of the hh execution context, and the promise job queue only has one entry.
As hh will run to completion the output "sync call" is the first output -- no job got a chance to run yet.
When hh() returns the JS event loop will pick up that one job and executes it. This is the callback that outputs "Second promise".
For the first question:
since promise1 and promise2 are resolved as soon as they are initialized, they will be in `resolved' state
in your code you call promise2.then(a=> console.log(a)) i.e. a=> console.log(a) will be pushed to a (asynchronous) execution task (asynchronous by fulfilled) later when this string function is complete
but now all hh functions are waiting for promise1<fulfilled> to finish so the default console.log(await promise1) and console.log('sync call') will become default synchronous functions with promise1
ending hh
and the function hh ends the synchronous execution continue to execute the waiting tasks now because the promise2 execution task added before it will be completely separated from the waiting task of hh it will be javascript executes because it is prepended resulting in the first function a => console.log(a) being executed
finish the task of promise2 continue to promise1 it works as synchronous code
Regarding the 2nd question
as I explained earlier a => console.log(a) is added to the task (asynchronously ready run by fulfilled) and hh does not depend on it leading to the next synchronous function continue executing console.log('sync call') then hh completes it continues executing promise2 task
then you call await promise1 it will also put the whole block of code afterwards (like you called .then above) into a task that executes (asynchronously)
This is achieved because javascript is a single-threaded language:
https://dev.to/bbarbour/if-javascript-is-single-threaded-how-is-it-asynchronous-56gd#:~:text=Javascript%20is%20a%20single%20threaded,times%20that%20can%20be%20harmful.
you can also achieve the above by using asynchronous functions like setTimeout...
More information at: https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Asynchronous/Introducing
Your promises are already resolved when your code reaches the last three operations. So, .then() is being called for promise2 and that schedules it to be picked up by the event loop once the current function is executed and this will happen under normal circumstances, this is why your second example first displays "sync call" and only then "Second Promise".
Yet, your await ensures that the current function is paused which allows the event loop to pick up the .then() of promise2 and evaluate the results of promise1. If you invert the order of your first and second line of the last three, then you get "First promise", "Second promise" and "sync call" being displayed.
async function hh(){
const promise1 = new Promise((resolve,reject) => {
resolve("First promise");
});
const promise2 = new Promise((resolve,reject) => {
resolve("Second promise");
});
console.log(await promise1);
promise2.then((a)=>console.log(a));
console.log("sync call");
}
hh();
A simple conversion of your initial code block into code that doesn't use async/await probably shows why console.log("sync call") isn't executed first.
async function hh(){
const promise1 = new Promise((resolve,reject) => {
resolve("First promise");
});
const promise2 = new Promise((resolve,reject) => {
resolve("Second promise");
});
promise2.then((a)=>console.log(a));
console.log(await promise1);
console.log("sync call");
}
hh();
function hh(){
const promise1 = new Promise((resolve,reject) => {
resolve("First promise");
});
const promise2 = new Promise((resolve,reject) => {
resolve("Second promise");
});
promise2.then((value) => console.log(value));
return promise1.then((value) => {
console.log(value);
console.log("sync call");
});
}
hh();
The above should give you a clear picture of why console.log("sync call") is not logged first.
Everything after an await becomes part of a then() callback, since the function is paused until the promise resolves. Statements are executed in sequential order, so console.log("sync call") can only be executed after console.log(await promise1).
console.log(a);
console.log(await promiseB);
console.log(c);
console.log(d);
console.log(await promiseE);
console.log(f);
console.log(await promiseG);
Can be written as:
console.log(a);
promiseB.then((b) => {
console.log(b);
console.log(c);
console.log(d);
return promiseE;
}).then((e) => {
console.log(e);
console.log(f);
return promiseG;
}).then((g) => {
console.log(g);
});
FIRST QUESTION ANSWER : You have suspended the Program's Execution by using the following line --> console.log(await promise1) until promise1 either gets resolved or rejected. During this suspended phase, the JS callstack is empty and the promise2 was already resolved and moved to the micro queue. Now, here comes the event loop. Event Loop sees that since the Callstack is empty, I should put whatever is present in my micro queue to the JS callstack and puts promise2's result (Second promise) to the call stack and it gets printed first and JS call stack becomes empty. Now,as you have suspended the execution of the code until promise1 resolves, First promise gets moved to the micro queue and again since the call stack is empty, Event Loop places the promise1's result on the call stack and First promise gets printed. After this, JS Single Thread moves to the next line and you see the sync call as your last printed statement
SECOND QUESTION ANSWER: The line promise2.then((a)=>console.log(a)); is asynchronus in nature, JS Single thread moves to the next line and push this line (console.log("sync call");) to the JS callstack. During this process, promise2 gets resolved and is moved to the Micro Queue. BUT Event Loop waits untill JS Call Stack is empty and will only push the promise2's result on the Call Stack, when console.log("sync call"); get executed and poppes out of the call stack. That's why you see sync call first and then,Second promise.

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.

Async and await in javascript [duplicate]

This question already has answers here:
Async await - does await block other code from running?
(5 answers)
Closed 2 years ago.
I am a beginner in JS and while going through async and await and I came across the below example:
const get = async () => {
const y = await "hello";
console.log(y);
}
console.log("start");
get();
console.log("end");
O/P
start
end
hello
But according to my understanding await blocks the execution of the current program until the promise is available, then why in the above example that doesn't happen? Is my understanding incorrect or some other concept is missing?
When an async function hits the first await in the function, it immediately suspends further execution of that function and then immediately returns a promise from the function. The caller receives that promise and continues to execute. So, the current function is suspended, but not the caller or the rest of the JS engine.
So, in your code:
const get=async ()=>{
const y=await "hello"
console.log(y)
}
console.log("start")
get()
console.log("end")
Here's the sequence of events:
Logs "start"
Starts to execute get()
Gets to the first await
Suspends further execution of the get() function
Returns promise from get()
Calling code continues to execute and logs "end"
When calling code returns back to the event loop, the await finishes because there was no actual promise there to wait for and the get() function resumes executing and it logs "hello"
So, await only suspends execution of the current function, causing it to immediately return a promise and the calling code then continues to execute. Sometime later when the statement you are awaiting resolves (usually a promise) and the interpreter has returned back to the event loop, then the function will continue its execution until either the next await or until it gets to its return value which then becomes the resolved value of the promise it originally returned and that promise gets resolved (which your code was ignoring).
Note, await should generally be used with promises, not with constants. Though it doesn't cause an error to await something that isn't a promise, it doesn't do anything useful.
await is not blocking the execution of the program, it defers the continuation of the currently executed async method to a microtask and continues executing the code of the async method caller.
When you await something that is not a Promise(string, number or whatever) it will automatically be wrapped in a Promise and that promise will be awaited.
So yes, everything works as expected in your code sample.
First of all async/await works with promises https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise
not with strings.
If you want your code to work, here is an example =)
const helloPromise = new Promise((resolve) => resolve('hello'));
const get = async () => {
const y = await helloPromise;
console.log(y);
}
const main = async () => {
console.log("start");
await get();
console.log("end");
}
main();

Confusion with how JS engine runs promises?

I am new to JS and was learning promises. So, let's say we have this code:
new Promise(function(resolve, reject) {
setTimeout(() => resolve(1), 1000); // (*)
}).then(function(result) { // (**)
alert(result); // 1
return result * 2;
})
As you can see the code above, when promise is invoked, setTimeout is run via callback queue. The question is When setTimeOut is sent to a browser, will JS engine omit .then() and continues running the rest of the code until the promise resolves? Secondly, async/await example:
async function showAvatar() {
// read our JSON
let response = await fetch('/article/promise-chaining/user.json');
let user = await response.json();
// read github user
let githubResponse = await fetch(`https://api.github.com/users/${user.name}`);
let githubUser = await githubResponse.json();
// show the avatar
let img = document.createElement('img');
img.src = githubUser.avatar_url;
img.className = "promise-avatar-example";
document.body.append(img);
// wait 3 seconds
await new Promise((resolve, reject) => setTimeout(resolve, 3000));
img.remove();
return githubUser;
}
showAvatar();
When showAvatar function is called, JS engine will encounter let response = await fetch('/article/promise-chaining/user.json'); and sends fetch to the browser to handle. The second question is Will JS engine wait until fetch gets resolved or Will JS engine continue executing let user = await response.json(); and the rest of the code inside showAvatar function? If so, how can JS engine handle response.json() since response is not received? Hope you got my point))).
Your first example works like this:
new Promise runs, calling the function you pass it (the executor function) synchronously
Code in the executor function calls setTimeout, passing in a function to call 1000ms later; the browser adds that to its list of pending timer callbacks
new Promise returns the promise
then is called, adding the function you pass into it to the promise's list of fulfillment handlers and creating a new promise (which your code doesn't use, so it gets thrown away).
1000ms or so later, the browser queues a call to the setTimeout callback, which the JavaScript engine picks up and runs
The callback calls the resolve function to fulfill the promise with the value 1
That triggers the promise's fulfillment handlers (asynchronously, but it doesn't really matter for this example), so the handler attached in Step 4 gets called, showing the alert and then returning result * 2 (which is 1 * 2, which is 1). That value is used to fulfill the promise created and thrown away in Step 4.
Will JS engine wait until fetch gets resolved or Will JS engine continue executing let user = await response.json();...
It waits. The async function is suspended at the await in await fetch(/*...*/), waiting for the promise fetch returned to settle. While it's suspended, the main JavaScript thread can do other things. Later, when the promise settles, the function is resumed and either the fulfillment value is assigned to response (if the promise is fulfilled) or an exception will get thrown (if it is rejected).
More generally: async functions are synchronous up until the first await or return in their code. At that point, they return their promise, which is settled later based on the remainder of the async function's code.
In a comment you asked:
when async function is suspended at each await, will async function is removed from the call stack and is put back to the call stack again when the promise being awaited settles?
At a low level, yes; but to make debugging easier, a good, up-to-date JavaScript engine maintains an "async call stack" they use for error traces and such. For instance, if you run this on Chrome...
function delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
async function inner() {
await delay(80);
throw new Error("boom");
}
async function outer() {
await inner();
}
function wrapper() {
outer()
.then(result => {
console.log(result);
})
.catch(error => {
console.log(error.stack);
});
}
wrapper();
...the stack looks like this:
Error: boom
at inner (https://stacksnippets.net/js:18:11)
at async outer (https://stacksnippets.net/js:22:5)
Notice the "async" prior to "outer," and also notice that wrapper isn't mentioned anywhere. wrapper is done and has returned, but the async functions were suspended and resumed.

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

Categories

Resources