Event Loop Core Principle Simulation
Task Queue Basic Structure
Let’s start by simulating a simplified version of the Event Loop’s core structure:
class EventLoopSimulator {
constructor() {
this.macroTaskQueue = []; // Macrotask queue
this.microTaskQueue = []; // Microtask queue
this.currentPhase = 'idle'; // Current phase
this.timerIdCounter = 1; // Timer ID counter
this.timers = new Map(); // Timer storage
}
// Add macrotask
addMacroTask(task) {
this.macroTaskQueue.push(task);
this.runEventLoop();
}
// Add microtask
addMicroTask(task) {
this.microTaskQueue.push(task);
}
// Simulate setTimeout
setTimeout(callback, delay) {
const timerId = this.timerIdCounter++;
const triggerTime = Date.now() + delay;
this.timers.set(timerId, {
callback,
triggerTime
});
// In real implementations, a system timer would be used
// We simplify by checking timers in runEventLoop
return timerId;
}
// Simulate clearTimeout
clearTimeout(timerId) {
this.timers.delete(timerId);
}
// Simulate process.nextTick (Node.js specific)
nextTick(callback) {
// nextTick queue has higher priority than microtasks
this.microTaskQueue.unshift(() => {
callback();
// nextTick callbacks may add new nextTick callbacks
// So we need to continue processing the nextTick queue
this.processNextTickQueue();
});
}
// Process nextTick queue
processNextTickQueue() {
while (this.microTaskQueue.length > 0 &&
this.microTaskQueue[0].isNextTick) {
const task = this.microTaskQueue.shift();
task();
}
}
// Run event loop
runEventLoop() {
if (this.currentPhase !== 'idle') return;
this.currentPhase = 'running';
// 1. Execute one macrotask
if (this.macroTaskQueue.length > 0) {
const macroTask = this.macroTaskQueue.shift();
console.log('Executing macrotask:', macroTask.name || 'anonymous');
macroTask();
}
// 2. Execute all microtasks
this.processMicroTasks();
// 3. Check timers (in real implementations, triggered by system timers)
this.checkTimers();
// 4. Rendering (browser environment)
// Simplified here
// 5. Check if new macrotasks need execution
if (this.macroTaskQueue.length > 0) {
// Continue next event loop iteration
setImmediate(() => this.runEventLoop());
} else {
this.currentPhase = 'idle';
}
}
// Process microtask queue
processMicroTasks() {
// Process nextTick queue first (if any)
this.processNextTickQueue();
// Then process regular microtasks
while (this.microTaskQueue.length > 0) {
const microTask = this.microTaskQueue.shift();
console.log('Executing microtask:', microTask.name || 'anonymous');
microTask();
}
}
// Check timers
checkTimers() {
const now = Date.now();
const expiredTimers = [];
// Find all expired timers
for (const [timerId, timer] of this.timers) {
if (timer.triggerTime <= now) {
expiredTimers.push(timerId);
}
}
// Execute expired timer callbacks (as macrotasks)
for (const timerId of expiredTimers) {
const timer = this.timers.get(timerId);
this.addMacroTask(() => {
console.log('Executing timer callback:', timerId);
timer.callback();
});
this.timers.delete(timerId);
}
}
}Complete Event Loop Simulation Implementation
Below is a more complete Event Loop simulation implementation, including macrotasks, microtasks, and nextTick queue:
class AdvancedEventLoopSimulator {
constructor() {
this.macroTaskQueue = []; // Macrotask queue
this.microTaskQueue = []; // Microtask queue
this.nextTickQueue = []; // nextTick queue (Node.js specific)
this.currentPhase = 'idle'; // Current phase
this.timerIdCounter = 1; // Timer ID counter
this.timers = new Map(); // Timer storage
this.animationFrameCallbacks = []; // requestAnimationFrame callbacks
this.isProcessing = false; // Whether event loop is processing
}
// Add macrotask
addMacroTask(task, name) {
this.macroTaskQueue.push({
task,
name: name || 'anonymous macro task'
});
this.runEventLoop();
}
// Add microtask
addMicroTask(task, name) {
this.microTaskQueue.push({
task,
name: name || 'anonymous micro task'
});
}
// Add nextTick task (Node.js specific)
nextTick(callback, name) {
this.nextTickQueue.push({
callback,
name: name || 'anonymous nextTick task'
});
this.runEventLoop();
}
// Simulate setTimeout
setTimeout(callback, delay, name) {
const timerId = this.timerIdCounter++;
const triggerTime = Date.now() + delay;
this.timers.set(timerId, {
callback: () => {
this.addMacroTask(() => {
console.log(`Executing timer callback(${name || timerId}):`);
callback();
});
},
triggerTime
});
// In real implementations, a system timer would be used
// We simplify by checking timers in runEventLoop
return timerId;
}
// Simulate clearTimeout
clearTimeout(timerId) {
this.timers.delete(timerId);
}
// Simulate setImmediate (Node.js specific)
setImmediate(callback, name) {
this.addMacroTask(() => {
console.log(`Executing setImmediate callback(${name || 'anonymous'}):`);
callback();
}, `setImmediate(${name || 'anonymous'})`);
}
// Simulate process.nextTick (Node.js specific)
processNextTick(callback, name) {
this.nextTick(callback, name);
}
// Simulate Promise.resolve().then() (microtask)
promiseThen(callback, name) {
this.addMicroTask(() => {
console.log(`Executing Promise microtask(${name || 'anonymous'}):`);
callback();
}, `promise.then(${name || 'anonymous'})`);
}
// Simulate requestAnimationFrame (browser specific)
requestAnimationFrame(callback, name) {
this.animationFrameCallbacks.push({
callback,
name: name || 'anonymous animation frame'
});
// In real implementations, called by browser on next frame
// We simplify by checking in runEventLoop
this.addMacroTask(() => {
if (this.animationFrameCallbacks.length > 0) {
const callbacks = this.animationFrameCallbacks.slice();
this.animationFrameCallbacks = [];
for (const { callback, name } of callbacks) {
console.log(`Executing requestAnimationFrame(${name || 'anonymous'}):`);
callback();
}
}
}, 'animation frame processing');
}
// Run event loop
runEventLoop() {
if (this.isProcessing) return;
this.isProcessing = true;
// Use setImmediate or setTimeout to simulate event loop iterations
// Simplified here with direct recursion
// Real implementations use system event loop mechanisms
setTimeout(() => {
this.processEventLoopPhase();
this.isProcessing = false;
// If tasks remain, continue next event loop iteration
if (this.macroTaskQueue.length > 0 ||
this.microTaskQueue.length > 0 ||
this.nextTickQueue.length > 0) {
this.runEventLoop();
}
}, 0);
}
// Process one event loop phase
processEventLoopPhase() {
console.log('--- Starting event loop phase ---');
// 1. Process nextTick queue (Node.js specific)
this.processNextTickQueue();
// 2. Process microtask queue
this.processMicroTaskQueue();
// 3. Process macrotask queue
this.processMacroTaskQueue();
// 4. Check timers
this.checkTimers();
// 5. Process requestAnimationFrame callbacks (browser specific)
this.processAnimationFrameCallbacks();
console.log('--- Ending event loop phase ---\n');
}
// Process nextTick queue
processNextTickQueue() {
while (this.nextTickQueue.length > 0) {
const { callback, name } = this.nextTickQueue.shift();
console.log(`Executing nextTick task(${name || 'anonymous'}):`);
callback();
// nextTick callbacks may add new tasks
// Continue processing until queue is empty
// Simplified here; Node.js implementation is more complex
}
}
// Process microtask queue
processMicroTaskQueue() {
while (this.microTaskQueue.length > 0) {
const { task, name } = this.microTaskQueue.shift();
console.log(`Executing microtask(${name || 'anonymous'}):`);
task();
}
}
// Process macrotask queue
processMacroTaskQueue() {
if (this.macroTaskQueue.length > 0) {
const { task, name } = this.macroTaskQueue.shift();
console.log(`Executing macrotask(${name || 'anonymous'}):`);
task();
}
}
// Check timers
checkTimers() {
const now = Date.now();
const expiredTimers = [];
// Find all expired timers
for (const [timerId, timer] of this.timers) {
if (timer.triggerTime <= now) {
expiredTimers.push(timerId);
}
}
// Execute expired timer callbacks (as macrotasks)
for (const timerId of expiredTimers) {
const timer = this.timers.get(timerId);
timer.callback();
this.timers.delete(timerId);
}
}
// Process requestAnimationFrame callbacks
processAnimationFrameCallbacks() {
if (this.animationFrameCallbacks.length > 0) {
const callbacks = this.animationFrameCallbacks.slice();
this.animationFrameCallbacks = [];
for (const { callback, name } of callbacks) {
console.log(`Executing requestAnimationFrame(${name || 'anonymous'}):`);
callback();
}
}
}
}Task Queues and Execution Flow
Macrotask and Microtask Queues
Macrotasks:
- script (entire code)
- setTimeout/setInterval
- I/O operations
- UI rendering (browser)
- setImmediate (Node.js)
- requestAnimationFrame (browser)
Microtasks:
- Promise.then/catch/finally
- process.nextTick (Node.js)
- MutationObserver (browser)
Event Loop Execution Flow Simulation
Let’s simulate a complete event loop execution flow:
// Create event loop simulator instance
const eventLoop = new AdvancedEventLoopSimulator();
// Simulate a series of tasks
console.log('Script start (macrotask)');
// Add some macrotasks
eventLoop.addMacroTask(() => {
console.log('Macrotask 1');
// Add microtask within macrotask
eventLoop.promiseThen(() => {
console.log('Microtask 1 (added in macrotask 1)');
}, 'microtask in macrotask1');
// Add nextTick within macrotask (Node.js)
eventLoop.processNextTick(() => {
console.log('nextTick 1 (added in macrotask 1)');
}, 'nextTick in macrotask1');
}, 'macrotask1');
eventLoop.addMacroTask(() => {
console.log('Macrotask 2');
// Add another macrotask (setTimeout) within macrotask
eventLoop.setTimeout(() => {
console.log('setTimeout callback (added in macrotask 2)');
// Add microtask within setTimeout callback
eventLoop.promiseThen(() => {
console.log('Microtask 2 (added in setTimeout callback)');
}, 'microtask in setTimeout');
}, 0, 'setTimeout in macrotask2');
}, 'macrotask2');
// Add microtask
eventLoop.promiseThen(() => {
console.log('Microtask 1 (directly added)');
}, 'direct microtask1');
// Add nextTick (Node.js)
eventLoop.processNextTick(() => {
console.log('nextTick 1 (directly added)');
}, 'direct nextTick1');
// Add another microtask
eventLoop.promiseThen(() => {
console.log('Microtask 2 (directly added)');
// Add macrotask (setImmediate) within microtask
eventLoop.setImmediate(() => {
console.log('setImmediate callback (added in microtask 2)');
}, 'setImmediate in microtask2');
}, 'direct microtask2');
// Add requestAnimationFrame (browser)
eventLoop.requestAnimationFrame(() => {
console.log('requestAnimationFrame callback');
}, 'animation frame');
console.log('Script end (macrotask)');Expected Output Analysis
Based on the Event Loop execution order, the expected output for the above code should be:
Script start (macrotask)
Script end (macrotask)
Executing nextTick task(direct nextTick1):
nextTick 1 (directly added)
Executing setImmediate callback(setImmediate in nextTick1):
setImmediate callback (added in nextTick 1)
Executing microtask(direct microtask1):
Microtask 1 (directly added)
Executing microtask(direct microtask2):
Microtask 2 (directly added)
Executing setImmediate callback(setImmediate in microtask2):
setImmediate callback (added in microtask 2)
Executing macrotask(macrotask1):
Macrotask 1
Executing microtask(microtask in macrotask1):
Microtask 1 (added in macrotask 1)
Executing nextTick task(nextTick in macrotask1):
nextTick 1 (added in macrotask 1)
Executing macrotask(macrotask2):
Macrotask 2
Executing macrotask(setTimeout in macrotask2):
setTimeout callback (added in macrotask 2)
Executing microtask(microtask in setTimeout):
Microtask 2 (added in setTimeout callback)
Executing requestAnimationFrame(animation frame):
requestAnimationFrame callback



