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 and setInterval
  • Geolocation and GeolocationPosition
  • localStorage and sessionStorage
  • Blob, File, and FileList
  • Performance and PerformanceMeasure
  • 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);

References