How Javascript Event Loop work
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:
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 fortest1()
is created, and the function begins to execute its body.
Execution Steps:
Step 1 (Synchronous code:
console.log('Start')
):console.log('Start')
is synchronous, so it gets executed first.Output:
Start
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.
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 thisPromise.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.
Step 4 (Synchronous code:
console.log('End')
):console.log('End')
is synchronous, so it gets executed next.Output:
End
Step 5 (End of synchronous code):
Now that the synchronous code in
test1()
has finished, the execution context fortest1()
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.
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
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:
Initial State:
Call Stack: Global Execution Context Web API: (empty) Callback Queue: (empty) Microtask Queue: (empty)
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)
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)
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()
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()
Step 5: After all synchronous code has executed:
Call Stack: (empty) Web API: (empty) Callback Queue: setTimeout() callback Microtask Queue: Promise.then()
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)
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.
Subscribe to my newsletter
Read articles from Vishal Rai directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by