The Microtask Queue And Priority
Not all queues are created equal. JavaScript has a VIP lane for Promise callbacks called the Microtask Queue.
We've seen two ways of getting background work back into JavaScript: the Callback Queue for old-school facades and the Promise object for new ones. But what happens if they both try to enter the Call Stack at the same time?
The Essentials
- Microtask Queue: A special high-priority queue specifically for functions deferred by Promises (via
.then). - Callback Queue (Task Queue): The standard queue for regular browser APIs like
setTimeoutand DOM events. - The Priority Loop: The Event Loop always checks the Microtask Queue before the Callback Queue.
- Starvation: If you keep adding to the Microtask Queue, the Callback Queue might never get a turn (though this is rare in practice).
The Great Race
Let's look at a complex scenario that illustrates the rules of the road:
function display(data) { console.log(data); }
function printHello() { console.log("Hello"); }
function blockFor300ms() { /* long loop */ }
setTimeout(printHello, 0);
const futureData = fetch('tiktok.com/will');
futureData.then(display);
blockFor300ms();
console.log("Me first!");The Timeline
- 0ms:
setTimeouttriggers a 0ms timer. It completes immediately.printHellois pushed into the Callback Queue. - 1ms:
fetchtriggers a network request. A Promise object is returned.displayis attached to itsonFulfilledarray. - 2ms:
blockFor300msbegins. The thread is now blocked. - 270ms: The network request returns
"cute puppy".futureData.valueis updated.displayis pushed into the Microtask Queue.
- 302ms:
blockFor300msfinishes. Global code continues to log "Me first!". - 303ms: The Call Stack is finally empty. The Event Loop kicks in.
The Decision
The Event Loop has a choice. printHello has been waiting in the Callback Queue since 0ms. display only arrived in the Microtask Queue at 270ms.
By all laws of fairness, printHello should go first. But JavaScript is not fair.
The Event Loop checks the Microtask Queue first. It finds display, dequeues it, and pushes it onto the Call Stack. "cute puppy" is logged.
Only after the Microtask Queue is completely empty does the Event Loop look at the Callback Queue. Finally, printHello gets its turn. "Hello" is logged last.
Step 1:At 270ms, the fetch completes. display enters the MICROTASK QUEUE. It is now waiting alongside printHello (who is in the Callback Queue).
Why the Priority?
Promises are usually associated with vital data and state changes. By giving them their own priority queue, JavaScript ensures that our application logic (like updating the UI with fetched data) happens as fast as possible, even if the Callback Queue is clogged with dozens of small timers or DOM events.
Now that we understand how the data gets in, what happens when things go wrong? In the final part, we'll look at Error Handling and the ability to Abort our background work.
Further Reading and Watching
- Video: Microtasks vs Macrotasks
- Concept: Event Loop Order of Operations
- Course: JavaScript: The Hard Parts (Microtasks)
Keep reading