JavaScript is single-threaded, so it handles time-consuming work using its asynchronous behavior instead of blocking the main thread. When an async task finishes in the browser, its callback is placed in the event queue. The event loop watches for an empty call stack and then moves the next queued callback to the stack, so async code like setTimeout runs only after the synchronous code is done.
The event queue:
- Stores completed async callbacks until they can run
- Works with the event loop to keep JavaScript responsive
- Let the synchronous code finish first before the queued tasks execute
.gif)
Example: To demonstrate the asynchronous nature of the JavaScript using the setTimeout method.
console.log("First part")
setTimeout(() => {
console.log("Second part")
}
, 0)
//waits for 0s(Asyncronous code)
console.log("Third part")
Output
First part
Third part
Seconf part
The result of the asnychronous part was available immediately but the output is printed last. It is because all the asynchronous code is executed after all syncronous code.
Working of Event Queue
The event queue operates as a first-in, first-out (FIFO) holding area for callbacks whose async operations have completed. Here is the step-by-step flow:
- Synchronous code begins executing and is pushed onto the call stack normally.
- When an async operation is encountered, it is handed off to the browser's Web APIs, and execution continues without waiting.
- Once the Web API finishes its work (e.g., the timer expires, the network response arrives), it places the associated callback into the event queue.
- The event loop continuously monitors the call stack. The moment the call stack becomes empty, it dequeues the first callback from the event queue and pushes it onto the stack for execution.
- This cycle repeats, keeping JavaScript responsive without blocking.
Note: Microtask queue callbacks (Promises) are always drained completely before the event loop picks the next callback from the macrotask/event queue, giving them higher effective priority.
Example 1: To demonstrate how a callback waits in the event queue until the call stack is clear.
function first() {
console.log("Synchronous: first()");
}
function second() {
console.log("Synchronous: second()");
}
setTimeout(() => {
console.log("Async callback from event queue");
}, 0);
first();
second();
Output
Synchronous: first()
Synchronous: second()
Async callback from event queue
Example 2: To demonstrate the working of the Asynch JavaScript in event loop.
console.log("First part")
setTimeout(() => {
console.log("Second part")
}
, 0)
//waits for 0s(Asyncronous code)
setTimeout(() => {
console.log("Second 2 part")
}
, 0)
//waits for 0s(Asyncronous code)
console.log("Third part")
Output
First part
Third part
Second part
Second 2 part
Microtask Queue
Microtasks are tasks that are executed asynchronously, but right after the currently executing script. They are usually high-priority tasks and are often used for things like promises and mutation observers.
In JavaScript, the microtask queue is commonly implemented using the Promise object. When a promise settles (fulfilled or rejected), its respective .then() and .catch() handlers are placed in the microtask queue.
Example: To demonstrate the micro task queue working using the console.log('End') statement that comes after the promises, it's logged before the microtasks because microtasks execute immediately after the current task is done executing.
console.log('Start');
Promise.resolve().then(() => {
console.log('Microtask 1')
});
Promise.resolve().then(() => {
console.log('Microtask 2')
});
console.log('End');
Output
Start
End
Microtask 1
Microtask 2
Macro Task Queue
Macrotasks are tasks that are executed asynchronously, but they are placed at the end of the event queue and executed after the microtasks. Common examples of macrotasks include setTimeout, setInterval, and DOM event handlers. In JavaScript, the macro task queue includes tasks like setTimeout, setInterval, and I/O operations.
Example: To demonsrtate the working of the Macro task queue in JavaScript.
console.log('Start');
setTimeout(() => console.log('Macro task 1'), 0);
setTimeout(() => console.log('Macro task 2'), 0);
console.log('End');
Output
Start
End
Microtask 1
Microtask 2
Implementation in Event Queue:
The event loop in JavaScript handles both microtasks and macrotasks. When an event occurs, it's placed in the appropriate queue. Microtasks are executed first, followed by macrotasks.
Example: To demonstrate the working of the micro and macro task queue in JavaScript.
console.log('Start');
Promise.resolve().then(() => console.log('Microtask 1'));
setTimeout(() => console.log('Macro task 1'), 0);
console.log('End');
Output
Start End Microtask 1 Macro task 1
Micro tasks have higher priority and are executed before macro tasks in the JavaScript event loop. They are often used for critical operations like handling promises or observing mutations. Macro tasks, on the other hand, are deferred tasks that are executed after micro tasks and are commonly associated with I/O events and timers.
console.log("Start");
setTimeout(() => {
console.log("Inside setTimeout->1 (macrotask)");
}, 0);
Promise.resolve().then(() => {
console.log("Inside Promise.then->1 (microtask)");
});
Promise.resolve().then(() => {
console.log("Inside Promise.then->2 (microtask)");
});
setTimeout(() => {
console.log("Inside setTimeout->2 (macrotask)");
}, 0);
console.log("End");
Output
Start End Inside Promise.then->1 (microtask) Inside Promise.then->2 (microtask) Inside setTimeout->1 (macrotask) Inside setTimeout->2 (macrotask)
The order of execution:
- "Start" is logged.
- Two setTimeout functions and two promise .then() functions are scheduled.
- "End of the script" is logged.
- Microtasks are executed. Both Promise.then() functions are executed in the order they were scheduled. So, "Inside Promise.then 1 (microtask)" and "Inside Promise.then 2 (microtask)" are logged.
- Macrotasks are executed. Both setTimeout functions are executed in the order they were scheduled. So, "Inside setTimeout 1 (macrotask)" and "Inside setTimeout 2 (macrotask)" are logged.
This demonstrates the execution order of tasks in both microtask and macrotask queues.