How Javascript Event Loop work

Vishal RaiVishal Rai
4 min read

In JavaScript, the execution context and the event loop determine how code, particularly asynchronous code, gets executed. The example provided involves asynchronous behavior (with setTimeout and Promise). Let's break it down step by step, explaining how the execution proceeds within the execution context and how the event loop handles different tasks.

Code Breakdown:

async function test1() {
  console.log('Start');

  // Asynchronous task (setTimeout)
  setTimeout(() => {
    console.log('Inside setTimeout');
  }, 0);

  // Microtask (Promise)
  Promise.resolve().then(() => {
    console.log('Inside Promise');
  });

  console.log('End');
}

test1();

Execution Process with Explanation:

  1. Call Stack and Execution Context:

    • When the function test1() is called, it gets pushed onto the call stack.

    • The JavaScript engine executes synchronous code from top to bottom.

    • Initially, we are inside the Global Execution Context. When test1() is invoked, a new Execution Context for test1() is created, and the function begins to execute its body.

Execution Steps:

  1. Step 1 (Synchronous code: console.log('Start')):

    • console.log('Start') is synchronous, so it gets executed first.

    • Output: Start

  2. Step 2 (Asynchronous task: setTimeout):

    • setTimeout is called, but it is asynchronous.

    • The Web APIs (provided by the browser or Node.js environment) handle the setTimeout function.

    • The callback function () => console.log('Inside setTimeout') is registered to run after the timer (which is 0 milliseconds in this case), but it won't be executed immediately. Instead, it is placed in the Callback Queue.

    • At this point, the setTimeout call returns, but its callback remains in the queue, waiting for the main thread to finish executing the current code.

  3. Step 3 (Microtask: Promise.resolve().then()):

    • The promise is created and resolved immediately (Promise.resolve()), so the callback attached to .then() is pushed to the Microtask Queue (also known as the Job Queue).

    • Microtasks have higher priority than tasks in the Callback Queue (like setTimeout), so this Promise.then() callback will be handled before any other task in the Callback Queue.

    • At this point, the promise callback (=> console.log('Inside Promise')) is waiting in the Microtask Queue for the current synchronous code to finish.

  4. Step 4 (Synchronous code: console.log('End')):

    • console.log('End') is synchronous, so it gets executed next.

    • Output: End

  5. Step 5 (End of synchronous code):

    • Now that the synchronous code in test1() has finished, the execution context for test1() is popped from the call stack, meaning the call stack is now empty.

    • The JavaScript engine now looks to handle any pending tasks from the Microtask Queue and Callback Queue.

  6. Step 6 (Microtask Queue: Promise resolution):

    • Since the Microtask Queue has higher priority than the Callback Queue, the promise's .then() callback (console.log('Inside Promise')) is taken from the Microtask Queue and pushed to the call stack.

    • The callback gets executed.

    • Output: Inside Promise

  7. Step 7 (Callback Queue: setTimeout):

    • Now that the Microtask Queue is empty, the event loop checks the Callback Queue.

    • The setTimeout callback (console.log('Inside setTimeout')) is taken from the Callback Queue and pushed to the call stack.

    • The callback gets executed.

    • Output: Inside setTimeout

Output Order:

The final output will be:

Start
End
Inside Promise
Inside setTimeout

How the Event Loop Works:

  • Synchronous code gets executed immediately in the order it's written.

  • Asynchronous tasks like setTimeout are moved to the Callback Queue to be handled later by the event loop.

  • Microtasks (like Promise.then()) are moved to the Microtask Queue, and they are given priority over tasks in the Callback Queue.


Diagram Representation:

  1. Initial State:

     Call Stack:
       Global Execution Context
     Web API:
       (empty)
     Callback Queue:
       (empty)
     Microtask Queue:
       (empty)
    
  2. Step 1: After console.log('Start'):

     Output: Start
    
     Call Stack:
       Global Execution Context
       test1() Execution Context
     Web API:
       (empty)
     Callback Queue:
       (empty)
     Microtask Queue:
       (empty)
    
  3. Step 2: After setTimeout() is called:

     Call Stack:
       Global Execution Context
       test1() Execution Context
     Web API:
       setTimeout() (waiting for 0ms)
     Callback Queue:
       (empty)
     Microtask Queue:
       (empty)
    
  4. Step 3: After Promise is created and .then() attached:

     Call Stack:
       Global Execution Context
       test1() Execution Context
     Web API:
       setTimeout() (waiting for 0ms)
     Callback Queue:
       (empty)
     Microtask Queue:
       Promise.then()
    
  5. Step 4: After console.log('End'):

     Output: End
    
     Call Stack:
       Global Execution Context
       test1() Execution Context
     Web API:
       setTimeout() (waiting for 0ms)
     Callback Queue:
       (empty)
     Microtask Queue:
       Promise.then()
    
  6. Step 5: After all synchronous code has executed:

     Call Stack:
       (empty)
     Web API:
       (empty)
     Callback Queue:
       setTimeout() callback
     Microtask Queue:
       Promise.then()
    
  7. Step 6: Event loop runs Promise callback (Microtask):

     Output: Inside Promise
    
     Call Stack:
       Global Execution Context
     Web API:
       (empty)
     Callback Queue:
       setTimeout() callback
     Microtask Queue:
       (empty)
    
  8. Step 7: Event loop runs setTimeout callback:

     Output: Inside setTimeout
    
     Call Stack:
       Global Execution Context
     Web API:
       (empty)
     Callback Queue:
       (empty)
     Microtask Queue:
       (empty)
    

Summary:

  • The Event Loop ensures that synchronous tasks are executed first, followed by asynchronous tasks (promises get higher priority via the Microtask Queue, followed by tasks like setTimeout in the Callback Queue).

  • This order of execution allows JavaScript to handle asynchronous behavior efficiently without blocking the main thread.

0
Subscribe to my newsletter

Read articles from Vishal Rai directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Vishal Rai
Vishal Rai