Node.js 宏任务与微任务详解
概述
是的,Node.js中确实有宏任务和微任务的概念!这是Node.js事件循环机制的核心组成部分。
什么是宏任务和微任务?
宏任务 (Macro Tasks)
宏任务是由事件循环的各个阶段处理的任务,包括:
javascript
// 宏任务示例
setTimeout(() => console.log('setTimeout'), 0); // Timer阶段
setImmediate(() => console.log('setImmediate')); // Check阶段
fs.readFile('file.txt', callback); // Poll阶段
setInterval(() => console.log('setInterval'), 100); // Timer阶段微任务 (Micro Tasks)
微任务是在每个事件循环阶段之间执行的任务,包括:
javascript
// 微任务示例
process.nextTick(() => console.log('nextTick')); // 最高优先级
Promise.resolve().then(() => console.log('Promise')); // Promise回调
queueMicrotask(() => console.log('queueMicrotask')); // 通用微任务APINode.js 事件循环详解
事件循环的6个阶段
┌───────────────────────────┐
┌─>│ timers │ ← setTimeout, setInterval
│ └─────────────┬─────────────┘
│ ┌─────────────┴─────────────┐
│ │ pending callbacks │ ← I/O异常回调
│ └─────────────┬─────────────┘
│ ┌─────────────┴─────────────┐
│ │ idle, prepare │ ← 内部使用
│ └─────────────┬─────────────┘
│ ┌─────────────┴─────────────┐
│ │ poll │ ← 获取新的I/O事件,执行I/O回调
│ └─────────────┬─────────────┘
│ ┌─────────────┴─────────────┐
│ │ check │ ← setImmediate回调
│ └─────────────┬─────────────┘
│ ┌─────────────┴─────────────┐
└──┤ close callbacks │ ← socket.on('close', ...)
└───────────────────────────┘微任务的执行时机
关键点:微任务在每个阶段之间执行!
每个阶段 → 执行微任务队列 → 下个阶段 → 执行微任务队列 → ...任务优先级
执行顺序(从高到低)
- 同步代码 - 立即执行
- process.nextTick - 微任务,最高优先级
- Promise.then/catch/finally - 微任务
- queueMicrotask - 微任务
- setImmediate - 宏任务(Check阶段)
- setTimeout/setInterval - 宏任务(Timer阶段)
- I/O操作回调 - 宏任务(Poll阶段)
微任务内部优先级
javascript
// 优先级:nextTick > Promise > queueMicrotask
Promise.resolve().then(() => console.log('Promise'));
process.nextTick(() => console.log('nextTick')); // 先执行
queueMicrotask(() => console.log('queueMicrotask'));
// 输出顺序:nextTick → Promise → queueMicrotask实际执行示例
基础示例
javascript
console.log('开始');
setTimeout(() => console.log('setTimeout'), 0);
setImmediate(() => console.log('setImmediate'));
Promise.resolve().then(() => console.log('Promise'));
process.nextTick(() => console.log('nextTick'));
console.log('结束');
// 输出顺序:
// 开始
// 结束
// nextTick
// Promise
// setTimeout
// setImmediate复杂嵌套示例
javascript
setTimeout(() => {
console.log('setTimeout');
Promise.resolve().then(() => console.log('Promise in setTimeout'));
process.nextTick(() => console.log('nextTick in setTimeout'));
}, 0);
Promise.resolve().then(() => {
console.log('Promise');
process.nextTick(() => console.log('nextTick in Promise'));
});
process.nextTick(() => {
console.log('nextTick');
Promise.resolve().then(() => console.log('Promise in nextTick'));
});
// 输出顺序:
// nextTick
// Promise
// Promise in nextTick
// nextTick in Promise
// setTimeout
// nextTick in setTimeout
// Promise in setTimeout特殊情况
1. I/O回调中的执行顺序
javascript
const fs = require('fs');
fs.readFile(__filename, () => {
setTimeout(() => console.log('setTimeout in I/O'), 0);
setImmediate(() => console.log('setImmediate in I/O'));
});
// 在I/O回调中,setImmediate优先于setTimeout
// 输出:setImmediate in I/O → setTimeout in I/O2. 微任务可能阻塞事件循环
javascript
// ⚠️ 危险:无限递归的微任务
function recursiveNextTick() {
process.nextTick(recursiveNextTick);
}
recursiveNextTick(); // 会阻塞事件循环!
// ✅ 安全:使用setImmediate避免阻塞
function safeRecursive() {
setImmediate(safeRecursive);
}
safeRecursive(); // 不会阻塞事件循环与浏览器的区别
| 特性 | Node.js | 浏览器 |
|---|---|---|
| 微任务API | process.nextTick, Promise | Promise, MutationObserver |
| 宏任务API | setTimeout, setImmediate, I/O | setTimeout, DOM事件 |
| 执行模型 | 事件循环6阶段 | 简化的事件循环 |
| nextTick | 存在,优先级最高 | 不存在 |
| setImmediate | 存在 | 不存在(部分浏览器支持) |
性能考虑
1. 微任务性能对比
javascript
// 性能排序(从快到慢)
process.nextTick() // 最快
Promise.resolve().then() // 中等
queueMicrotask() // 较慢2. 避免微任务饥饿
javascript
// ❌ 可能导致饥饿
for (let i = 0; i < 1000000; i++) {
process.nextTick(() => {/* 大量微任务 */});
}
// ✅ 分批处理
function processBatch(items, batchSize = 1000) {
const batch = items.splice(0, batchSize);
batch.forEach(item => process.nextTick(() => processItem(item)));
if (items.length > 0) {
setImmediate(() => processBatch(items, batchSize));
}
}实际应用场景
1. 错误处理
javascript
// 使用process.nextTick确保错误处理在同步代码之后
function asyncFunction(callback) {
if (typeof callback !== 'function') {
process.nextTick(() => {
throw new TypeError('Callback must be a function');
});
return;
}
// 异步操作...
}2. API一致性
javascript
// 确保API始终异步执行
function maybeAsync(data, callback) {
if (cache.has(data)) {
// 即使有缓存,也要异步返回
process.nextTick(() => callback(null, cache.get(data)));
} else {
fetchData(data, callback);
}
}3. 状态同步
javascript
// 确保状态更新在下一个tick执行
class EventEmitter {
emit(event, ...args) {
const listeners = this.listeners[event];
if (listeners) {
process.nextTick(() => {
listeners.forEach(listener => listener(...args));
});
}
}
}调试技巧
1. 使用console.trace跟踪执行顺序
javascript
setTimeout(() => console.trace('setTimeout'), 0);
Promise.resolve().then(() => console.trace('Promise'));
process.nextTick(() => console.trace('nextTick'));2. 监控事件循环延迟
javascript
const { performance } = require('perf_hooks');
function measureEventLoopDelay() {
const start = performance.now();
setImmediate(() => {
const delay = performance.now() - start;
console.log(`事件循环延迟: ${delay.toFixed(2)}ms`);
});
}
setInterval(measureEventLoopDelay, 1000);最佳实践
- 优先使用Promise而不是process.nextTick
- 避免在微任务中执行CPU密集型操作
- 使用setImmediate而不是setTimeout(fn, 0)
- 合理控制微任务数量,避免饥饿
- 在I/O回调中优先使用setImmediate
总结
Node.js的宏任务和微任务机制是其高性能异步处理的基础:
- 微任务在每个事件循环阶段之间执行,优先级高
- 宏任务按事件循环的6个阶段顺序执行
- process.nextTick是Node.js特有的最高优先级微任务
- 理解执行顺序有助于编写更可预测的异步代码
- 合理使用可以提升应用性能和用户体验