Skip to main content

Microtask Queue

Promises/Async/Await: Microtask Queue



What is the Microtask Queue in JavaScript?

View Answer:
Interview Response: The Microtask Queue is a task queue in the JavaScript event loop that processes microtasks, which are small, short-lived tasks created by asynchronous operations like Promises, MutationObserver, or queueMicrotask.

How does the Microtask Queue differ from the Task Queue?

View Answer:
Interview Response: Microtask Queue has higher priority than Task Queue. Microtasks are executed immediately after the current synchronous code finishes, before any other tasks, like rendering or I/O callbacks, are processed.

What is the role of Promises in the Microtask Queue?

View Answer:
Interview Response: Promises use the Microtask Queue to schedule the execution of their "then" and "catch" callbacks, ensuring these callbacks are executed after the current synchronous code finishes and before any other tasks.

Can you explain how the Microtask Queue is processed?

View Answer:
Interview Response: After the current synchronous code finishes, the Microtask Queue is processed. All micro tasks are executed one by one until the queue is empty. Then, the event loop proceeds to the Task Queue.

What is the purpose of the queueMicrotask() function?

View Answer:
Interview Response: The queueMicrotask() function allows developers to directly enqueue a microtask in the Microtask Queue. This enables scheduling the execution of a callback after the current synchronous code and before other tasks.

Code Example:

Let's say we want to defer a piece of code until the current task and all other microtasks have completed. We can use queueMicrotask() for this purpose:

console.log('Script start');

queueMicrotask(() => {
console.log('Microtask 1');
});

queueMicrotask(() => {
console.log('Microtask 2');
});

console.log('Script end');

// Output:
// Script start
// Script end
// Microtask 1
// Microtask 2

In the example above, 'Script start' and 'Script end' are logged first. Even though we queued two microtasks, they don't run until after the script has completed.


Can you explain how the Job Queue (Microtasks Queue) works?

View Answer:
Interview Response: The Job Queue, or Microtasks Queue, holds promises' callbacks for execution after the current synchronous task completes but before returning control to the event loop, ensuring "Promise then" code executes promptly.

Technical Response: Apart from Callback Queue, browsers have introduced one more queue, the “Job Queue”, reserved only for new Promise() functionality. Asynchronous tasks need proper management. The ECMA standard specifies an internal queue PromiseJobs, more often referred to as the “microtask queue” (V8 term). So when you use promises in your code, you add .then() method, which is a callback method. These `thenable` methods are added to Job Queue once the promise has returned/resolved and then executes. Notably, any code in the ScriptsJob returns before the return of a promise in the microtasks queue.

Code Example:

console.log('Message no. 1: Sync');

setTimeout(function () {
console.log('Message no. 2: setTimeout');
}, 0);

var promise = new Promise(function (resolve, reject) {
resolve();
});

promise
.then(function (resolve) {
console.log('Message no. 3: 1st Promise');
})
.then(function (resolve) {
console.log('Message no. 4: 2nd Promise');
});

console.log('Message no. 5: Sync');

// Expected Output:
// Message no. 1: Sync
// Message no. 5: Sync
// Message no. 2: setTimeout
// Message no. 3: 1st Promise
// Message no. 4: 2nd Promise

How can we make code run after a promise completes if the order matters to us?

View Answer:
Interview Response: You can ensure code runs after a promise by attaching a `.then()` method to the promise. The callback provided to `.then()` executes once the promise is resolved.

Code Example:

Promise.resolve()
.then(() => console.log('promise done!'))
.then(() => console.log('code finished'));

When does an unhandled rejection occur in JavaScript promises?

View Answer:
Interview Response: An unhandled rejection occurs in JavaScript promises when a Promise rejects (fails) and there is no associated `catch()` method to handle the error or rejection.

Code Example:

let promise = Promise.reject(new Error('Promise Failed!'));
promise.catch((err) => console.log('caught'));

// doesn't run: error handled
window.addEventListener('unhandledrejection', (event) => console.log(event.reason));

//////////////////////////////////////

// Example: if we don't handle our errors

let promise = Promise.reject(new Error('Promise Failed!'));
promise.catch((err) => console.log('caught'));

// doesn't run: error handled
window.addEventListener('unhandledrejection', (event) => console.log(event.reason));

//////////////////////////////////////

// Example: if we handle errors later in our code
let promise = Promise.reject(new Error('Promise Failed!'));
setTimeout(() => promise.catch((err) => console.log('caught')), 1000); // handling error 1 second later

// Error: Promise Failed!
window.addEventListener('unhandledrejection', (event) => console.log(event.reason));

Can the Microtask Queue be blocked by long-running synchronous code?

View Answer:
Interview Response: Yes, the Microtask Queue can be blocked by long-running synchronous code, as it only runs tasks after the current synchronous execution completes and before yielding control back to the event loop.

Code Example:

console.log('Script start');

const start = Date.now();
while(Date.now() - start < 5000) {} // long-running synchronous code, blocks for 5 seconds

queueMicrotask(() => {
console.log('Microtask executed');
});

console.log('Script end');

// Output:
// Script start
// Script end (after 5 seconds)
// Microtask executed

In this example, the microtask is blocked by the long-running synchronous while loop and only executes after that code completes.


What happens if a microtask enqueues another microtask?

View Answer:
Interview Response: If a microtask enqueues another microtask in JavaScript, it's added to the Microtask Queue and will execute in the same microtask checkpoint, before returning control to the event loop.

What is the difference between using setTimeout() and queueMicrotask() to schedule a task?

View Answer:
Interview Response: `setTimeout()` schedules a macrotask, which will run after the current execution context and microtask queue are empty. `queueMicrotask()` schedules a microtask, which runs before control returns to the event loop.

Code Example:

console.log('Script start');

setTimeout(() => {
console.log('Macrotask: setTimeout callback');
}, 0);

queueMicrotask(() => {
console.log('Microtask: queueMicrotask callback');
});

console.log('Script end');

// Output:
// Script start
// Script end
// Microtask: queueMicrotask callback
// Macrotask: setTimeout callback

Even though setTimeout is called before queueMicrotask, the microtask executes first. This is because the Microtask Queue is processed immediately after the current task completes and before returning to the event loop, whereas setTimeout schedules a macrotask, which will only execute after control returns to the event loop.


How does the MutationObserver API use the Microtask Queue?

View Answer:
Interview Response: The MutationObserver API uses the Microtask Queue to schedule callbacks. After any DOM mutations, these callbacks are added to the Microtask Queue to be executed before the next render or event loop tick.

Code Example:

Here's an example. Let's say we want to watch for changes to a DOM element. We can use the MutationObserver API for this purpose, and its callbacks will be queued in the Microtask Queue:

let div = document.createElement('div');

let observer = new MutationObserver(() => {
console.log('Mutation observed');
});

observer.observe(div, { attributes: true });

console.log('Script start');

div.setAttribute('id', 'test');

console.log('Script end');

// Output:
// Script start
// Script end
// Mutation observed

In this example, even though the mutation (the attribute change) happens before 'Script end' is logged, the callback isn't called until after the script completes, demonstrating that MutationObserver uses the Microtask Queue.


View Answer:
Interview Response: The Microtask Queue in JavaScript is typically used for tasks requiring immediate execution after the current task, like promise callbacks or MutationObserver callbacks, and before control returns to the event loop.