Event Loop

JavaScript is a synchronous, single-threaded language. It operates with one call stack, which is part of the JavaScript engine, and can execute only one task at a time. All JavaScript code is executed within this call stack.

When code is run, a Global Execution Context (GEC) is created and placed in the call stack. Any task added to the call stack is executed immediately. However, certain tasks, like setTimeout or network requests, cannot be handled directly by the call stack because it lacks built-in mechanisms like timers or network handling capabilities.

For such asynchronous tasks, JavaScript relies on Web APIs provided by browsers. These APIs enable functionalities like setTimeout, DOM manipulation, fetch(), localStorage, console, and location.

Web APIs are accessible via the window object in the browser. For example, calling window.setTimeout allows you to use the setTimeout function. However, you typically don’t need to prefix these features with window because the window object is global. This means its properties and methods, like setTimeout, are directly accessible without explicitly referencing window.

In summary, the browser wraps additional features in the window object and provides them as interfaces that work alongside the JavaScript call stack. This seamless integration enables JavaScript to handle both synchronous and asynchronous tasks effectively.

CASE 1

setTimeout and Console Execution

Consider the following code:

console.log("Start");

setTimeout(function cb() {
  console.log("callback");
}, 5000);

console.log("End");

This code demonstrates how the JavaScript event loop works in conjunction with setTimeout. Below is the step-by-step breakdown:


Step-by-Step Execution

  1. console.log("Start") Execution

    • The console.log("Start") statement is added to the Call Stack.

    • It executes immediately, logging "Start" to the browser console.

    • After execution, it is removed from the Call Stack.

  2. setTimeout Execution

    • When setTimeout is encountered:

      • It requests access to the browser's Timer feature.

      • The callback function (cb) is registered in the browser's Web APIs environment.

      • The timer begins counting down for 5000ms.

  3. console.log("End") Execution

    • The next statement, console.log("End"), is added to the Call Stack.

    • It executes immediately, logging "End" to the browser console.

    • After execution, it is removed from the Call Stack.

  4. Call Stack Becomes Empty

    • At this point, both console.log statements have executed, and the Global Execution Context (GEC) is cleared from the Call Stack.
  5. Timer in the Web APIs

    • While the above steps occur, the setTimeout timer is running asynchronously in the Web APIs section.

    • Once the 5000ms timer is complete, the callback function (cb) is moved to the Callback Queue.

  6. Callback Queue and Event Loop

    • The Callback Queue holds the cb function until the Call Stack is empty.

    • The Event Loop continuously monitors the Call Stack. As soon as it detects the Call Stack is clear, it pushes the callback function (cb) from the Callback Queue into the Call Stack for execution.

  7. Callback Execution

    • The cb function is added to the Call Stack.

    • It executes, logging "callback" to the browser console.

    • After execution, it is removed from the Call Stack.

Execution Order in the Browser Console

Start
End
callback

CASE 2

Understanding DOM APIs and Console Execution

Consider the following code:

console.log("Start");

document.getElementById("btn")
        .addEventListener("click", function cb() {
  console.log("Callback");
});

console.log("End");

This code demonstrates how JavaScript interacts with the DOM and the event loop through addEventListener. Below is a step-by-step explanation:


Step-by-Step Execution

  1. console.log("Start") Execution

    • The console.log("Start") statement is added to the Call Stack.

    • It executes immediately, logging "Start" to the browser console.

    • After execution, it is removed from the Call Stack.

  2. addEventListener Execution

    • When addEventListener is called:

      • It accesses the DOM API, which is provided by the browser as part of the window object.

      • The DOM API parses the HTML source code to locate the element with the ID "btn".

      • The callback function (cb) is registered in the Web APIs and associated with the click event of the btn element.

  3. console.log("End") Execution

    • The next statement, console.log("End"), is added to the Call Stack.

    • It executes immediately, logging "End" to the browser console.

    • After execution, it is removed from the Call Stack.

  4. Callback Registration in Web APIs

    • The callback function registered in the Web APIs is now waiting for the click event on the btn element.

    • This registration persists until either:

      • The browser/tab is closed, or

      • The event listener is explicitly removed.

  5. Button Click and Callback Execution

    • When the button (btn) is clicked:

      • The browser triggers the associated click event.

      • The registered callback function (cb) is moved to the Callback Queue.

  6. Event Loop and Callback Execution

    • The Event Loop continuously monitors the Call Stack.

    • As soon as the Call Stack is empty, the cb function is moved from the Callback Queue into the Call Stack.

    • The cb function is then executed, logging "Callback" to the browser console.

    • After execution, the cb function is removed from the Call Stack.


Execution Order in the Browser Console

The browser console will display the following output in order:

Start
End
// "Callback" will appear only after the button is clicked

Understanding fetch(), setTimeout, and Console Execution

Consider the following code:

console.log("Start");

setTimeout(function cBT() {
  console.log("CB Set Time out");
}, 5000);

fetch("https://api.netflix.com")
  .then(function cbF() {
    console.log("CB Netflix");
  });

console.log("End");

Step-by-Step Execution

  1. console.log("Start") Execution

    • The console.log("Start") statement is added to the Call Stack.

    • It executes immediately, logging "Start" to the browser console.

    • After execution, it is removed from the Call Stack.

  2. setTimeout Execution

    • The setTimeout function is encountered:

      • It accesses the Timer feature of the browser.

      • The callback function (cBT) is registered in the Web APIs, and a timer is started for 5000ms.

  3. fetch() Execution

    • The fetch function initiates an API request to https://api.netflix.com:

      • The Web APIs handle the network request asynchronously.

      • When the response is received from the API server, the .then() callback (cbF) is registered in the Microtask Queue.

  4. console.log("End") Execution

    • The next statement, console.log("End"), is added to the Call Stack.

    • It executes immediately, logging "End" to the browser console.

    • After execution, it is removed from the Call Stack.

  5. Processing the Microtask Queue

    • Once the Call Stack is empty, the Event Loop gives priority to the Microtask Queue over the Callback Queue.

    • The cbF function (from the fetch promise) is moved to the Call Stack and executed:

      • "CB Netflix" is logged to the browser console.
    • After execution, cbF is removed from the Call Stack.

  6. Processing the Callback Queue

    • After the Microtask Queue is emptied, the Event Loop moves to the Callback Queue.

    • Once the 5000ms timer completes, the cBT function is moved from the Callback Queue to the Call Stack and executed:

      • "CB Set Time out" is logged to the browser console.
    • After execution, cBT is removed from the Call Stack.


Execution Order in the Browser Console

The browser console will display the following output in this order:

Start
End
CB Netflix
CB Set Time out

Key Points

  1. setTimeout:

    • Registers its callback in the Web APIs and schedules it to move to the Callback Queue after the timer expires.
  2. fetch():

    • Uses the Web APIs to handle the API request.

    • When the response is received, its callback is placed in the Microtask Queue, which has a higher priority than the Callback Queue.

  3. Event Loop Priorities:

    • The Event Loop processes tasks from the Microtask Queue before the Callback Queue, ensuring promises and other asynchronous operations are resolved sooner.
  4. Call Stack:

    • Tasks are always executed from the Call Stack, whether they originate from the Microtask Queue or the Callback Queue.

What is Prioritized in the Microtask Queue?

  1. Promise Callbacks (.then, .catch, .finally)

    • Promises are one of the most common sources of microtasks. When a promise is resolved or rejected, its associated .then, .catch, or .finally callback is added to the microtask queue.
  2. MutationObserver Callbacks

    • The MutationObserver API, used to detect DOM changes, places its callbacks in the microtask queue for efficient handling.
  3. Queue Microtask API (queueMicrotask)

    • Developers can explicitly add a task to the microtask queue using the queueMicrotask() function.