Async in JavaScript
So I thought I might just do some basic JS because outside projects I barely ever do normal JS. And went to do some async programming to understand it better.
For this whole thing I used Google Gemini for understanding and reasoning. So just a heads up because everything I discuss is from my understanding and I can be wrong.
Lets see a code:
console.log("1: One");
setTimeout(() => {
console.log("2: Two");
}, 0);
Promise.resolve().then(function () {
console.log("3: Three");
});
console.log("4: Four");
The output of this snippet is :
1: One
4: Four
3: Three
2: Two
Lets understand how and why !
To understand this we need to understand the control flow of JavaScript. JS is a single threaded language means only one "worker" is running the whole program.
So the "worker" starts at the top sees a synchronous block console.log() and prints it. Moving forward it sees a setTimeout block which is a asynchronous block, immediately it schedules a timer with the host environment and continues with it execution. Moving forward it sees a Promise.resolve() which has a .then() attached to it therefore it schedules a micro task since the Promise is already resolved. Lastly the "worker" encounters another console.log() which is sync block and executes it as is. Hence we see the 1 and 4 logs before anything.
Next the output is 3 and not 2 which is strange because the setTimeout is set to 0 ms which is immediate and also the Promise is resolved immediately so it should also be done. Therefore the output should have been
2: Two
3: Three
But is the opposite because of how JS treats Promises !
Now there are 2 types of tasks - Micro Tasks and Macro Task and 2 separate queues are maintained for each. Micro Task have higher priority than Macro Tasks.
A Promise is a micro task therefore it is popped of the queue first than that of a setTimeout which is a Macro Task. And that is the very reason that the then block is executed first and shown even if the setTimeout comes before.
But does that mean no matter what a Promise's task will be executed before even if its taking time to resolve or reject the Promise and another low priority job is already completed ?
Not quite...Let see a modification of our code !
console.log("1: One");
setTimeout(() => {
console.log("2: Two");
}, 0);
Promise.resolve().then(function () {
setTimeout(() => {
console.log("3: Three");
}, 2000);
console.log("This prints after the settimeout inside the then block");
});
console.log("4: Four");
This piece of code here mandatorily forces a setTimeout to check that if the .then block makes the setTimeout outside it wait.
The output:
1: One
4: Four
This prints after the settimeout inside the then block
2: Two
3: Three # Prints after 2 seconds
Now this reveals something pretty interesting...
The first 2 are expected since they are sync code. But then we see the big line getting logged then the setTimeout outside and lastly the setTimeout inside the .then block.
Lets see what happened - So similar to our previous case then .then does execute first before the setTimeout outside, what happens is that as soon as the .then is executed by the event loop it sees another setTimeout (Macro Task) and registers it to execute normally just as any other async block and the control moves forward and prints the big line, execution of .then finishes.
Now we got only 2 setTimeouts (Macro Tasks) one to execute in 0 ms and one in 2000 ms (2 sec), the outside one finishes first and gets logged followed by the second one.
Note: The Event Loop only starts picking tasks from the Micro/Macro queues after the Call Stack is completely empty (i.e. all initial synchronous code has finished).
This is a pretty cool async programming thing in JavaScript.