do you know javascript is a single-threaded
language? but using a few smarter data structures, it gives us the illusion of multi-threaded
.
It means that the main thread where JavaScript code is run, runs in one line at a time manner and there is no possibility of running code in parallel.
The event loop is the main algorithm behind the multi-threading illusion, its have mainly three components, call stack
, event queue
, and Heap memory.
let's have a look in depth at the components.
Call Stack
The call stack
is a fundamental concept in the event loop of JavaScript.
The call stack is a data structure that keeps the track of the function calls that are currently being executed. When a function is called its associated information is added to the top of the call stack. when the function finishes execution, its information is removed from the top of the stack and the program continues executing the next function in the stack.
The call stack plays a very critical role in how the event loop works. When an event is processed, the associated callback function is added to the top of the call stack, if the callback function makes some additional function calls they are also added to the top.
If the call stack becomes too large, it can cause a stack overflow error and crash the program.
To prevent the call stack from becoming too large, JavaScript uses an asynchronous programming model. When an asynchronous function is called, its associated callback function is not immediately added to the call stack. Instead, the function is added to a queue, and the event loop checks the queue periodically to see if there are any callbacks waiting to be executed. If the call stack is empty, the event loop
Event Queue
In javascript, the event queue is a data structure used by the event loop to manage and execute asynchronous events
. The event queue is part of the javascript runtime environment, which is responsible for executing javascript code.
In web browser, the event queue can store events such as mouse click
, keyboard events
, and HTTP requests
. When an event is triggered
, it is added to the event queue
.
The event loop constantly checks the event queue to see if there are any events waiting
to be processed. if any are found, the event loop dequeues the events and processes them by pushing the appropriate event handler functions to the call stack.
JavaScript provides several mechanisms for asynchronous
programming, such as callbacks, Promises, and async/await
. These mechanisms allow developers to write code that can perform I/O operations or other time-consuming tasks without blocking the main thread of execution. The event loop and the event queue play a crucial role in enabling asynchronous programming in JavaScript.
Task Queue
A task queue
is a generic term used to describe any queue that holds tasks or functions that need to be executed at some point in the future. This can include both callback functions and other types of tasks that are not necessarily associated with a specific event or event handler.
Microtask Queue
The microtask queue, also known as the job queue or promise queue, is a queue of microtasks that are scheduled to run after the current task has finished but before the next task is executed. Microtasks are typically used for callbacks that need to be executed immediately after the current task, such as Promises
, Object.observe()
, and MutationObserver
.
Memory Heap
The memory heap
in JavaScript is the region of memory used by the engine to allocate and manage memory for objects and variables, while the event loop is responsible for managing the execution of code in response to events and for managing the memory heap.
The event loop checks the event queue
for pending events and executes the associated event handlers. As part of executing code, the event loop interacts with the memory heap to allocate and deallocate memory for objects and variables. If the memory heap becomes full, the JavaScript engine may trigger a garbage collection process to free up memory that is no longer in use.
let's see the particle with some visuals.
Example-1
1 function count(message) {
2 // emulate time consuming task
3 let n = 10000000000;
4 while (n > 0){
5 n--;
6 }
7 console.log(message);
8 }
9
10 console.log('Start script...');
11 count('Call an simple function');
12 console.log('Done!');
/*output:
Start script
Call an simple function
Done
*/
as you can see in the output because the code doesn't contain any event or callback or promise
.
in the absence of a special feature like event/callback/promise
, the code flow will work synchronously
. the compiler will wait till the count function doesn't complete its process. in this image we show the call stack.
Example-2
let's change the code of wait()
function and add a setTimeout()
function there and see what will be the output and how this will work.
1 function wait(message) {
2 // emulate time consuming task
3 setTimeout(()=>{
4 console.log(message)
5 }, 3000)
6 }
7
8 console.log('Start script...');
9 wait('In setTimeOut() web api.');
10 console.log('Done!');
/*
OUTPUT
Start script...
Done!
In setTimeOut() web api.
*/
Example-3
this example uses two queues task queue/ callback queue and microtask queue
, I have already told you about them.
1 function logA() { console.log('A') }
2 function logB() { console.log('B') }
3 function logC() { console.log('C') }
4 function logD() { console.log('D') }
5
6 logA();
7 setTimeout(logB, 0);
8 Promise.resolve().then(logC);
9 logD();
let's dive into the image,
in the code example you can see we have defined four functions and called them.
at line 6, function logA()
invoked and console A, the setTimeout()
function called with a delay of 0 milliseconds
, it went to web API
block and pushed to the Task queue simultaneously Promise also started resolving and added to Web API
. Now the compiler will run function logD()
, and it printed D
on the console.
till now the promise has also been resolved, the callback is pushed to the microtask queue and the call stack will also be empty.
due to hight priority
of the microtask queue
over the task queue
, the compiler will start pulling processes from the microtask queue and push to call stack that ran the logC()
.
the same thing happens with the task queue
when the microtask queue
got empty and in order to run the task queue processed it will console B.
hence the final output will be A D C B
.