Node.js 微任务与宏任务
微任务优先于宏任务执行,理解两者的调度规则是掌握异步编程的关键。
任务分类
微任务(Microtask)
JavaScript
// 微任务类型
Promise.then/catch/finally
queueMicrotask()
process.nextTick(Node.js 特有,最高优先级)
宏任务(Macrotask)
JavaScript
// 宏任务类型
setTimeout
setInterval
setImmediate(Node.js 特有)
I/O 回调
执行优先级
优先级从高到低:
- process.nextTick(nextTick 队列)
- Promise.then(微任务队列)
- setTimeout/setInterval(timers 阶段)
- setImmediate(check 阶段)
- I/O 回调(poll 阶段)
nextTick 与 Promise 对比
JavaScript
process.nextTick(() => console.log('nextTick'));
Promise.resolve().then(() => console.log('promise'));
console.log('sync');
// 输出: sync -> nextTick -> promise
Node.js 中 nextTick 队列独立于微任务队列,优先级更高:
JavaScript
// 执行顺序
process.nextTick(() => console.log('1'));
process.nextTick(() => console.log('2'));
Promise.resolve().then(() => console.log('3'));
Promise.resolve().then(() => console.log('4'));
process.nextTick(() => console.log('5'));
// 输出: 1, 2, 5, 3, 4
// 先清空 nextTick 队列,再清空微任务队列
queueMicrotask
JavaScript
// queueMicrotask 添加微任务
queueMicrotask(() => {
console.log('微任务');
});
// 等同于 Promise.resolve().then()
Promise.resolve().then(() => {
console.log('微任务');
});
微任务执行时机
每个阶段结束时,执行微任务:
JavaScript
setTimeout(() => {
console.log('timer');
Promise.resolve().then(() => console.log('微任务1'));
}, 0);
setTimeout(() => {
console.log('timer2');
Promise.resolve().then(() => console.log('微任务2'));
}, 0);
// 输出: timer -> 微任务1 -> timer2 -> 微任务2
// 每个 timer 后清空微任务队列
实例分析
JavaScript
console.log('A');
setTimeout(() => {
console.log('B');
Promise.resolve().then(() => console.log('C'));
}, 0);
Promise.resolve()
.then(() => console.log('D'))
.then(() => console.log('E'));
process.nextTick(() => {
console.log('F');
Promise.resolve().then(() => console.log('G'));
});
console.log('H');
// 执行顺序分析:
// 1. 同步: A, H
// 2. nextTick: F(触发微任务 G)
// 3. 微任务: D, E, G
// 4. timers: B(触发微任务 C)
// 5. 微任务: C
// 输出: A, H, F, D, E, G, B, C
微任务递归问题
JavaScript
// 微任务无限循环(阻塞事件循环)
function infiniteMicrotask() {
Promise.resolve().then(infiniteMicrotask);
}
infiniteMicrotask();
// 事件循环被阻塞,定时器无法执行
// nextTick 无限循环
function infiniteNextTick() {
process.nextTick(infiniteNextTick);
}
infiniteNextTick();
// 同样阻塞
避免在微任务中无限递归,会阻塞事件循环。
微任务与宏任务对比
| 特性 | 微任务 | 宏任务 |
|---|---|---|
| 执行时机 | 当前阶段后 | 下一阶段 |
| 优先级 | 高于宏任务 | 低 |
| 队列 | 每阶段后清空 | 每阶段执行部分 |
| 代表 | Promise.then | setTimeout |
setImmediate 与 setTimeout
JavaScript
// 非I/O回调中顺序不确定
setTimeout(() => console.log('timeout'), 0);
setImmediate(() => console.log('immediate'));
// I/O回调中 setImmediate 先执行
fs.readFile('file.txt', () => {
setTimeout(() => console.log('timeout'), 0);
setImmediate(() => console.log('immediate'));
});
// 输出: immediate -> timeout
原因:I/O 回调在 poll 阶段执行,下一阶段是 check(setImmediate)。
监控微任务执行
JavaScript
const { performance } = require('perf_hooks');
performance.on('resourceresolve', (item) => {
console.log('Promise resolved');
});
// 检测微任务耗时
const timerify = performance.timerify;
const wrapped = timerify(asyncFunction);
要点总结
- process.nextTick 优先级最高,独立队列
- Promise.then 在 nextTick 之后执行
- 每阶段结束后清空微任务队列
- 微任务递归会阻塞事件循环
- I/O 回调中 setImmediate 优先于 setTimeout
📝 发现内容有误?点击此处直接编辑