Event Loop & Call Stack in JavaScript - Exercise 3
It occurs when code continually adds microtasks, preventing the event loop from reaching regular tasks such as timers. This ordering explains why asynchronous work cannot interrupt the synchronous code that is already running.
function keepBusy() {
queueMicrotask(keepBusy);
}
// keepBusy(); // starves the task queueA long-running function occupies the call stack, so timers, input, rendering, and network callbacks must wait. This ordering explains why asynchronous work cannot interrupt the synchronous code that is already running.
function blockFor(milliseconds) {
const start = Date.now();
while (Date.now() - start < milliseconds) {}
}
blockFor(200);Process a small batch and schedule the next batch as a task so other work can run between batches. This ordering explains why asynchronous work cannot interrupt the synchronous code that is already running.
function processBatch(items, index = 0) {
const nextIndex = Math.min(index + 100, items.length);
for (let i = index; i < nextIndex; i += 1) process(items[i]);
if (nextIndex < items.length) setTimeout(() => processBatch(items, nextIndex), 0);
}Promise handlers are microtasks, which are drained before the event loop takes the next task from the callback queue. This ordering explains why asynchronous work cannot interrupt the synchronous code that is already running.
button.addEventListener('click', () => console.log('click task'));
Promise.resolve().then(() => console.log('promise microtask'));
// The Promise handler runs before a later click task.Tasks include timers and DOM events; microtasks include Promise reactions and queueMicrotask callbacks. Microtasks run between tasks. The example makes the scheduling boundary visible without relying on implementation-specific timing.
setTimeout(() => console.log('task'), 0);
queueMicrotask(() => console.log('microtask'));
// microtask runs before taskIt schedules visual updates before the browser's next repaint, making it suitable for animation rather than arbitrary background work. This ordering explains why asynchronous work cannot interrupt the synchronous code that is already running.
let position = 0;
function animate() {
position += 1;
box.style.transform = `translateX(${position}px)`;
requestAnimationFrame(animate);
}
requestAnimationFrame(animate);Use bounded batches, yield with a task scheduler, and avoid unbounded synchronous loops or microtask chains. This ordering explains why asynchronous work cannot interrupt the synchronous code that is already running.
async function processAll(items) {
for (const item of items) {
process(item);
await new Promise(resolve => setTimeout(resolve, 0));
}
}Label synchronous calls, task scheduling, microtask scheduling, and each callback, then trace when the stack empties and which queue has priority. This ordering explains why asynchronous work cannot interrupt the synchronous code that is already running.
console.log('A');
setTimeout(() => console.log('D'), 0);
Promise.resolve().then(() => console.log('C'));
console.log('B');
// A, B, C, DCalling an async function runs synchronously until await. The continuation after await is scheduled as a microtask once the awaited promise settles.
async function read() {
console.log('before');
await Promise.resolve();
console.log('after');
}
read();
console.log('outside');Mark each synchronous statement, promise reaction, and timer, then process them in stack, microtask, and task order. This turns complex output into smaller decisions.
Promise.resolve().then(() => console.log('microtask'));
setTimeout(() => console.log('task'), 0);
console.log('sync');
// sync, microtask, task