Intro
The Call Stack, Task Queue, Event Loop, Microtask Queue combined allow us to run async JS code, this is important because JS isn’t multithreaded. The JS engine runs on a single thread.
Call Stack
The call Stack manages execution of the program. Every time an execution context is created (expression or operation) it’s pushed on the stack and executed. Every time something’s pushed onto the call stack, JS can only run the tasks one at a time, meaning only the Stack’s head is executed.
This is problematic in the real world, when conducting long running tasks such as network ( HTTP) requests and that’s where Web APIs come in.
Web APIs
These provide interfaces that allow interacting with the browser’s features.
Examples of these interfaces include:
fetch
setTimeout
andsetInterval
Geolocation
andGeolocationPosition
localStorage
andsessionStorage
Blob
,File
, andFileList
Performance
andPerformanceMeasure
URL
The browser is a powerful platform with many powerful components, including the Rendering Engines, Network Service, and GPU. It also provides hardware specific ones like sensors, file system, storage engines, cameras, mics, geolocation…etc.
Some of these APIs allow us to offload these tasks to the browser to handle and thus don’t block other tasks on the website such as interactivity. An example of this is making HTTP requests.
Callbacks resulting from the offloaded tasks aren’t pushed back onto the Call Stack as that might interrupt and ongoing task. Instead they’re placed into the Task Queue.
Task Queue
Callbacks are placed onto here, which is why it’s also called Callback Queue, for the Call Stack to then pick them up. This queue holds all the callbacks and event handlers to get executed in the future.
This is where the Event Loop comes in.
Event Loop
Its responsibility is to check when the Call Stack is empty. If it is empty, then it gets the next task in the Task Queue and pushes it onto the Call Stack for execution.
Microtask Queue
Remember Promises? Well this queue is all about Promises.
It’s dedicated to then
, catch
, and finally
callbacks. As well as the function body after an await
, the callbacks in queueMicroTask()
function, and MutationObserver
object instantiation.
The Event Loop actually prioritizes the Microtask queue over the Task Queue, so if the Call Stack is empty but both queues aren’t, then it’ll favor pushing from the Microtask queue.
Microtask Spawning
Microtasks can schedule other microtasks to the Microtask Queue, which games the Event Loop into focusing solely on the Microtask Queue and hogs the Call Stack…
All together example
Based on what you’ve read and your understanding of this loop, what will get logged?
Promise.resolve().then(() => console.log(1));
setTimeout(() => console.log(2), 10);
queMicrotask(()=>{
console.log(3);
queMicrotask(()=> console.log(4))
});
console.log(5);