Open In App

Event Queue in JavaScript

Last Updated : 02 May, 2024
Improve
Improve
Like Article
Like
Save
Share
Report

JavaScript, being single-threaded, processes tasks sequentially, meaning it executes one task at a time. This can pose a challenge when dealing with operations that take time to complete, such as fetching data from a server or performing complex calculations. To handle such scenarios efficiently, JavaScript employs asynchronous behavior.

The-Secret-Behind-Steady-App-Performance-Asynchronous-JavaScript1

The Async JavaScript

Call Stack

The call stack is a mechanism that JavaScript uses to keep track of its execution context. Whenever a function is invoked, a corresponding frame is pushed onto the call stack. This frame contains information about the function’s arguments, local variables, and the line of code currently being executed. Once a function completes its execution, its frame is popped off the stack.

Asynchronous Behavior

Asynchronous behavior allows JavaScript to execute non-blocking code. This means that instead of waiting for a time-consuming operation to complete, JavaScript can continue executing other tasks while waiting for the result. Common examples of asynchronous operations include fetching data from an API, reading files, or executing setTimeout.

When an asynchronous operation is encountered, it is off loaded to the browser’s APIs (such as XMLHttpRequest, setTimeout, or fetch) to handle. JavaScript continues executing other tasks in the meantime.

Event Queue and Event Loop

The-Secret-Behind-Steady-App-Performance-Asynchronous-JavaScript-(1)

Event queue and Event loop

While JavaScript is busy executing other tasks, the asynchronous operation is carried out by the browser in the background. Once the operation is completed, its result is placed in the event queue.

The event loop continuously monitors the call stack and the event queue. When the call stack is empty (i.e., there are no pending synchronous tasks), the event loop picks the first item from the event queue and pushes it onto the call stack for execution. This process ensures that asynchronous tasks are executed in the order they were completed, without blocking the main thread. So the asynchronous function like setTimeout in this case is executed after all synchronous code.

Example: To demonstrate the asynchronous nature of the JavaScript using the setTimeut.

JavaScript
console.log("First part")

setTimeout(() => {
    console.log("Second part")
}
    , 500)
//waits for 0.5s (Asyncronous code)

console.log("Third part")

Output
First part
Third part
Second part

Example: To demonstrate the asynchronous nature of the JavaScript using the setTimeout method.

JavaScript
console.log("First part")

setTimeout(() => {
    console.log("Second part")
}
    , 0)
//waits for 0s(Asyncronous code)

console.log("Third part")

Output
First part
Third part
Second 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.

Example: To demonstrate the working of the Asynch JavaScript in event loop.

JavaScript
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

In JavaScript event queues, micro and macro task queues play crucial roles in managing asynchronous operations. Here’s an overview of each:

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.

JavaScript
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.

JavaScript
console.log('Start');

setTimeout(() => console.log('Macro task 1'), 0);
setTimeout(() => console.log('Macro task 2'), 0);

console.log('End');

Output
Start
End
Macro task 1
Macro task 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.

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.

JavaScript
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.



Like Article
Suggest improvement
Previous
Next
Share your thoughts in the comments

Similar Reads