EN/CH Mode
BRUCE_FE JS Interview Notes - Event Loop and Asynchronous Operations
Complete analysis of JavaScript and Node.js event loop, including microtask/macrotask differences, browser and Node.js execution order, nextTick and Promise priorities, and common interview pitfalls and solutions.
EN/CH Mode
Lazy to read articles? Then watch videos!
What are Asynchronous Operations?
JavaScript is a single-threaded language that cannot handle multiple tasks simultaneously. To prevent blocking the entire program during waiting operations (like API requests or timers), JavaScript introduced asynchronous mechanisms. These async tasks are handled by the browser or Node.js at a lower level, then queued back through the event loop for execution when completed.
- •
setTimeout
: macrotask, executes after delay - •
Promise.then
: microtask, executes immediately after synchronous code - •
async/await
: syntactic sugar for Promises - •
queueMicrotask
: manually add to microtask queue
Browser Event Loop
In the browser, each round of the event loop follows this execution order:
- 1.Execute synchronous code (Call Stack)
- 2.Clear all Microtask queue
- 3.Execute one Macrotask
- 4.Repeat the cycle
Microtasks are all executed before the end of each event loop cycle, taking priority over any macrotask.
console.log('1');
setTimeout(() => {
console.log('2');
}, 0);
Promise.resolve().then(() => {
console.log('3');
});
console.log('4');
輸出順序:1 → 4 → 3 → 2
requestAnimationFrame Position in Event Loop
requestAnimationFrame (rAF) is a browser API used for optimizing animation performance. It has a special position in the event loop:
Browser frame processing order:
1. Execute synchronous code
2. Execute all microtasks (Promise.then, queueMicrotask, etc.)
3. Execute requestAnimationFrame callbacks
4. Browser rendering (layout, paint, composite)
5. Execute a macrotask (setTimeout, setInterval, etc.)
6. Return to step 1 to start the next cycle
Node.js Event Loop
Node.js event loop is based on libuv, with six phases, each having its own corresponding tasks. Between each phase, all microtasks (including process.nextTick and Promise) are executed first.
- 1.timers: handle
setTimeout
/setInterval
- 2.pending callbacks: callbacks for some async operations
- 3.idle, prepare: internal use
- 4.poll: wait for new I/O events
- 5.check: execute
setImmediate
- 6.close callbacks: such as
socket.on('close')
process.nextTick(() => console.log('tick'));
Promise.resolve().then(() => console.log('promise'));
setTimeout(() => console.log('timeout'), 0);
console.log('sync');
輸出順序:sync → tick → promise → timeout
Microtask vs Macrotask Summary
- 🟢Microtask (executes immediately after sync):
Promise.then
,queueMicrotask
,MutationObserver
,async/await
- 🟡Macrotask (next event loop cycle):
setTimeout
,setInterval
,setImmediate
- 🔴Special: In Node.js,
process.nextTick
has priority over all microtasks
setTimeout vs setImmediate
In Node.js, if setTimeout
and setImmediate
appear in an I/O callback, setImmediate
has priority.
// 在 I/O callback 中,順序固定
const fs = require('fs');
fs.readFile(__filename, () => {
setTimeout(() => console.log('timeout'));
setImmediate(() => console.log('immediate'));
});
// 輸出:immediate → timeout
// 直接執行,順序不固定
setTimeout(() => console.log('timeout'), 0);
setImmediate(() => console.log('immediate'));
// 輸出:順序不固定!
Reason: The setTimeout(fn, 0)
in the main program is actually set to 1ms, and the slight difference in the timing of the event loop startup will affect the execution order.🔥 Common Interview Questions
1. What is Event Loop?
解答:
- Microtasks and Macrotasks:
JavaScript is single-threaded, doing one thing at a time. To handle asynchronous operations, JavaScript divides tasks into two categories:
- Microtasks: Such as
Promise.then
,queueMicrotask
,MutationObserver
- Macrotasks: Such as
setTimeout
,setInterval
,MessageChannel
, andsetImmediate
in Node.js
Execution order: 'Synchronous code → All microtasks → One macrotask → Repeat'
- Microtasks: Such as
- Differences between Node.js and browsers:
In browsers, each event loop cycle executes all synchronous code and microtasks first, then takes one task from the macrotask queue.
In Node.js, the event loop has six phases:
timers
→pending callbacks
→poll
→check
→close callbacks
. After each phase, Node.js runsprocess.nextTick
and Promise microtasks.process.nextTick
: Node.js specific, highest priorityPromise.then
: A microtask, prioritized over macrotaskssetImmediate
: Only exists in Node.js, executes in the check phase
2. Event Loop Order
console.log('A');
setTimeout(() => console.log('B'), 0);
Promise.resolve().then(() => console.log('C'));
console.log('D');
Answer: Output A → D → C → B
- 1. First execute synchronous code, printing A and D
- 2.
setTimeout
is a macrotask, placed in the next event loop cycle - 3.
Promise.then
is a microtask, executed immediately after synchronous code, printing C - 4. Finally, execute the
setTimeout
callback, printing B
3. Asynchronous Behavior of setTimeout vs setImmediate
setTimeout(() => console.log('timeout'), 0);
setImmediate(() => console.log('immediate'));
Answer: Output order is not fixed (could be timeout → immediate, or immediate → timeout)
- Explanation: Since this code isn't wrapped in an I/O callback, the execution order cannot be guaranteed and depends on the execution environment and event loop scheduling timing.
setTimeout
in the timers phasesetImmediate
in the check phase- Since this code isn't wrapped in an I/O callback, the execution order cannot be guaranteed and depends on the execution environment and event loop scheduling timing.
4. nextTick vs Promise
process.nextTick(() => console.log('tick'));
Promise.resolve().then(() => console.log('promise'));
console.log('sync');
Answer: Output sync → tick → promise
- 1. Synchronous code executes first, printing sync
- 2.
process.nextTick
has priority over all microtasks, printing tick - 3.
Promise.then
is a microtask, executed last and printing promise
5. Promise in setTimeout
setTimeout(() => {
console.log('1');
Promise.resolve().then(() => console.log('2'));
}, 0);
Promise.resolve().then(() => console.log('3'));
console.log('4');
Answer: Output 4 → 3 → 1 → 2
- 1. First execute synchronous code, printing 4
- 2.
Promise.then
is a microtask, immediately executed and printing 3 - 3.
setTimeout
is a macrotask, executed in the next event loop cycle, printing 1 - 4.
Promise.then
insidesetTimeout
is executed immediately aftersetTimeout
completes, printing 2
6. async/await and Event Loop
async function foo() {
console.log('A');
await Promise.resolve();
console.log('B');
}
console.log('C');
foo();
console.log('D');
Answer: Output C → A → D → B
Analysis: async/await
breaks at the await
point, placing subsequent logic as a microtask in the queue.
7. Mixed Execution Order of setTimeout + Promise + async/await
console.log('start');
setTimeout(() => {
console.log('timeout');
}, 0);
Promise.resolve().then(() => {
console.log('promise');
});
(async () => {
console.log('async1 start');
await Promise.resolve();
console.log('async1 end');
})();
console.log('end');
Answer: Output start → async1 start → end → promise → async1 end → timeout
Analysis:
- Synchronous code executes first:
start
,async1 start
,end
- Then enter the microtask queue, first executing
promise
, thenasync1 end
- Finally, the macrotask
setTimeout
8. What is the Relationship Between requestAnimationFrame and Event Loop?
A: requestAnimationFrame has a special execution timing in the event loop. It is called after all microtasks have finished executing, before rendering:
Event loop frame execution order:
1. Execute synchronous tasks
2. Execute microtask queue (Promise.then, queueMicrotask, etc.)
3. Execute requestAnimationFrame callbacks ← Special timing point
4. Browser rendering (layout, paint, composite)
5. Execute macrotasks (setTimeout, setInterval, etc.)
6. Enter next frame cycle
This allows rAF to update the screen at the last moment before rendering,
ensuring animation calculations are immediately rendered without delay to the next frame.
console.log('script start');
setTimeout(() => {
console.log('setTimeout 1');
Promise.resolve().then(() => {
console.log('microtask inside setTimeout 1');
});
}, 0);
Promise.resolve().then(() => {
console.log('microtask 1');
});
Promise.resolve().then(() => {
console.log('microtask 2');
});
setTimeout(() => {
console.log('setTimeout 2');
}, 0);
requestAnimationFrame(() => {
console.log('rAF callback');
});
console.log('script end');
Output order analysis:
- First execute synchronous tasks:
script start
→script end
- Then execute all microtasks in the current frame:
microtask 1
→microtask 2
- Then execute the requestAnimationFrame callback:
rAF callback
(before the next repaint) - Browser performs rendering
- Finally execute macrotasks:
setTimeout 1
→microtask inside setTimeout 1
(note this microtask is inside the setTimeout callback, so it executes immediately) →setTimeout 2
Complete output order: script start
→ script end
→ microtask 1
→ microtask 2
→ rAF callback
→ setTimeout 1
→ microtask inside setTimeout 1
→ setTimeout 2