Please predict the output along with the event queue reasoning of the below snippet.
I ran the snippet and I know the output but I would like to understand the event queue working and what gets higher preference and thus which logs first.
Here is the snippet:
console.log("Start");
setTimeout(() => console.log("Timeout 1"), 0);
async function asyncFunc() {
console.log("Async Start");
await new Promise((resolve) => setTimeout(() => resolve(), 0));
console.log("Async End");
}
asyncFunc();
Promise.resolve().then(() => console.log("Promise 1"));
setTimeout(() => console.log("Timeout 2"), 0);
console.log("End");
Please predict the output along with the event queue reasoning of the below snippet.
I ran the snippet and I know the output but I would like to understand the event queue working and what gets higher preference and thus which logs first.
Here is the snippet:
console.log("Start");
setTimeout(() => console.log("Timeout 1"), 0);
async function asyncFunc() {
console.log("Async Start");
await new Promise((resolve) => setTimeout(() => resolve(), 0));
console.log("Async End");
}
asyncFunc();
Promise.resolve().then(() => console.log("Promise 1"));
setTimeout(() => console.log("Timeout 2"), 0);
console.log("End");
My prediction for the above snippet:
("start","Async Start","End","Async End","Promise 1","Timeout 1","Timeout 2")
as promises queue up at microtask, timeouts queue up at callback queue, general console.log() are executed synchronously unless they are awaited by a previous statement which moves them to the microtask queue ..(My understanding)
However this logic totally fails to explain why the output is
("Start" ,"Async Start", "End" , "Promise 1" , "Timeout 1" , "Async End" ,"Timeout 2")
(actual output got by running in the editor)
Where am I wrong in my understanding?
Share Improve this question edited Mar 31 at 10:19 0stone0 44.4k5 gold badges52 silver badges80 bronze badges asked Mar 31 at 9:24 Racerr9Racerr9 195 bronze badges New contributor Racerr9 is a new contributor to this site. Take care in asking for clarification, commenting, and answering. Check out our Code of Conduct. 9 | Show 4 more comments3 Answers
Reset to default 1My prediction is promises queue up at microtask, timeouts queue up at callback queue
Not quite. It's promise handlers (the callbacks you pass to .then()
) and callback functions (that were passed to setTimeout
) that are getting queued. (Actually, it's a "task" or "job" object, which stores which function to execute, with which arguments, and what to do with the result).
And further, these tasks do not get queued immediately when they are defined. They are getting queued when the thing they were waiting for happens - the promise fulfills or rejects, or the timeout is reached.
In particular, the new Promise
you were await
ing, fulfills only when the second timeout occurs, and that (where resolve()
is called) is when the promise handler is scheduled on the microtask queue.
general
console.log()
are executed synchronously unless they are awaited by a previous statement which moves them to the microtask queue
Yeah, and it's not just console.log()
statements but any code after an await
. Really what's happening internally is that the remainder of the async function
is put in a "resumption function" that is put on the await
ed promise as a .then()
handler. Same as if you had written
function asyncFunc() {
console.log("Async Start");
return new Promise((resolve) => {
setTimeout(() => resolve(), 0);
}).then(() => {
console.log("Async End");
});
}
So again, the () => { console.log("Async End"); }
isn't scheduled to be executed until the promise fulfills.
Where am I wrong in my understanding?
You seem to have assumed that all tasks are immediately put into a queue. In your previous question you used only immediately-fulfilled promises (Promise.resolve()
), here you're still using setTimeout
that does not really wait (0ms) - but such things are the exception rather than the norm. We write asynchronous code because it has to wait for something happening (outside of the JavaScript execution) much later.
I would suggest to run the following code to understand better what's going on:
console.log("Start");
setTimeout(() => console.log("Timeout 1 (after 100ms)"), 100);
console.log("Timeout 1 created");
async function asyncFunc() {
console.log("Async Start");
const promise = new Promise((resolve) => {
console.log("In `new Promise` executor");
setTimeout(() => {
console.log("Timeout 2 (after 300ms)");
resolve();
}, 300);
console.log("Timeout 2 created");
});
console.log("inner promise created");
await promise;
console.log("Async End (after timeout 2)");
}
const p1 = asyncFunc();
console.log("p1 created");
const p2 = Promise.resolve().then(() => console.log("Promise 1"));
console.log("p2 created");
setTimeout(() => console.log("Timeout 3 (after 200ms)"), 200);
console.log("Timeout 3 created");
console.log("End");
It could help to list all actions in the order they are executed, together with the effect they have on the queues used by promise reactions and timeouts.
First, it will be easier to name all callback functions and promises involved. So here is the same script with assignment to variables that can be referenced in the above-mentioned list:
const t1 = () => console.log("Timeout 1");
const t2 = () => console.log("Timeout 2");
const p1_then = () => console.log("Promise 1");
const executor = (resolve) => setTimeout(resolve, 0);
console.log("Start");
setTimeout(t1, 0);
async function asyncFunc() {
console.log("Async Start");
const p0 = new Promise(executor);
await p0;
console.log("Async End");
}
const p3 = asyncFunc();
const p1 = Promise.resolve();
const p2 = p1.then(p1_then);
setTimeout(t2, 0);
console.log("End");
And now to the list:
The first column represents the callstack with as last entry the function that is currently executing an action.
For each of the involved promise objects there is a column listing its state (P=pending, F=fulfilled).
Finally, there are two columns that represent the current state of a queue: first the high-priority Promise Job Queue, and then the lower-priority task queue where timer jobs get queued.
Call stack | Action | p0 | p1 | p2 | p3 | Promise Job Queue | Task queue |
---|---|---|---|---|---|---|---|
Script | console.log("Start") |
||||||
Script | setTimeout(t1, 0) |
t1 |
|||||
Script | asyncFunc() |
t1 |
|||||
Script > asyncFunc |
console.log("Async Start") |
t1 |
|||||
Script > asyncFunc |
new Promise(executor) |
t1 |
|||||
Script > asyncFunc > executor |
setTimeout(resolve, 0) |
t1 , resolve |
|||||
Script > asyncFunc |
p0 = .... |
P | t1 , resolve |
||||
Script > asyncFunc |
await p0 |
P | t1 , resolve |
||||
Script | p3 = .... |
P | P | t1 , resolve |
|||
Script | p1 = Promise.resolve() |
P | F | P | t1 , resolve |
||
Script | p2 = p1.then(p1_then) |
P | F | P | P | p1_then |
t1 , resolve |
Script | setTimeout(t2, 0) |
P | F | P | P | p1_then |
t1 , resolve , t2 |
Script | console.log("End") |
P | F | P | P | p1_then |
t1 , resolve , t2 |
(empty) | check promise job queue | P | F | P | P | p1_then |
t1 , resolve , t2 |
p1_then |
console.log("Promise 1") |
P | F | P | P | t1 , resolve , t2 |
|
p1_then |
return (fulfill p2 ) |
P | F | F | P | t1 , resolve , t2 |
|
(empty) | check promise job queue | P | F | F | P | t1 , resolve , t2 |
|
(empty) | check other queues | P | F | F | P | t1 , resolve , t2 |
|
t1 |
console.log("Timeout 1") |
P | F | F | P | resolve , t2 |
|
(empty) | check promise job queue | P | F | F | P | resolve , t2 |
|
(empty) | check other queues | P | F | F | P | resolve , t2 |
|
resolve |
fulfill p0 | F | F | F | P | resume asyncFunc |
t2 |
(empty) | check promise job queue | F | F | F | P | resume asyncFunc |
t2 |
asyncFunc (resumed) |
console.log("Async End") |
F | F | F | P | t2 |
|
asyncFunc |
return (fulfill p3 ) |
F | F | F | F | t2 |
|
(empty) | check promise job queue | F | F | F | F | t2 |
|
(empty) | check other queues | F | F | F | F | t2 |
|
t2 |
console.log("Timeout 2") |
F | F | F | F | ||
(empty) | check promise job queue | F | F | F | F | ||
(empty) | check other queues | F | F | F | F |
Hope this helps.
Output would be - Start Async Start End Promise 1 Timeout 1 Async End Timeout 2
Explanation -
Firstly Synchronous Calls would be executed, therefore it would print start.
Then the synchronous call inside the Async function would be printed i.e. Async Start.
And further it would print another synchronous call i.e. End.
Now, Microtask runs before macrotasks, so it would print Promise 1.
Its the turn for Macrotask (setTimeout) runs and prints Timeout 1.
Now AsyncEnd is printed as its the turn of the async function to completely executed.
At the end another macrotask runs and prints Timeout 2.
console.log
in thenew Promise
callback and one in thesetTimeout
callback before theresolve()
call. That might clear it up what's happening with the 3 timeouts. – Bergi Commented Mar 31 at 9:59