Event  loop

Event loop

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.

Meme of the day