您现在的位置是:网站首页 > 宏任务与微任务文章详情
宏任务与微任务
陈川
【
Node.js
】
25289人已围观
4219字
宏任务与微任务的概念
宏任务和微任务是JavaScript中任务队列的两种类型,它们决定了代码的执行顺序。宏任务包括setTimeout、setInterval、I/O操作等,而微任务则包括Promise.then、process.nextTick等。理解它们的区别对于编写高效的异步代码至关重要。
console.log('script start');
setTimeout(() => {
console.log('setTimeout');
}, 0);
Promise.resolve().then(() => {
console.log('promise1');
}).then(() => {
console.log('promise2');
});
console.log('script end');
事件循环中的执行顺序
JavaScript引擎在处理异步代码时遵循特定顺序:
- 执行当前宏任务
- 执行所有微任务
- 渲染UI(浏览器环境)
- 执行下一个宏任务
setTimeout(() => console.log('timeout1'), 0);
setTimeout(() => {
console.log('timeout2');
Promise.resolve().then(() => console.log('promise inside timeout'));
}, 0);
Promise.resolve().then(() => console.log('promise1'));
Promise.resolve().then(() => console.log('promise2'));
Node.js中的特殊处理
Node.js的事件循环与浏览器略有不同,它分为多个阶段:
- timers:执行setTimeout和setInterval回调
- pending callbacks:执行系统操作回调
- idle, prepare:内部使用
- poll:检索新的I/O事件
- check:执行setImmediate回调
- close callbacks:执行关闭事件回调
setImmediate(() => console.log('immediate'));
setTimeout(() => console.log('timeout'), 0);
process.nextTick(() => console.log('nextTick'));
Promise.resolve().then(() => console.log('promise'));
微任务的优先级差异
在Node.js中,process.nextTick的优先级高于Promise:
- process.nextTick队列
- Promise微任务队列
- 其他微任务
process.nextTick(() => console.log('nextTick1'));
process.nextTick(() => {
console.log('nextTick2');
process.nextTick(() => console.log('nextTick inside nextTick'));
});
Promise.resolve().then(() => console.log('promise1'));
process.nextTick(() => console.log('nextTick3'));
实际应用中的陷阱
不正确的任务使用可能导致性能问题或意外行为:
// 递归的process.nextTick会导致I/O饥饿
function recursiveNextTick() {
process.nextTick(() => {
console.log('running nextTick');
recursiveNextTick();
});
}
// 更合理的做法是使用setImmediate
function betterRecursive() {
setImmediate(() => {
console.log('running setImmediate');
betterRecursive();
});
}
宏任务与微任务的性能影响
微任务会在当前宏任务结束后立即执行,而宏任务需要等待事件循环的下一个周期:
function measurePerformance() {
console.time('microtasks');
let count = 0;
function runMicrotasks() {
if (count++ < 10000) {
Promise.resolve().then(runMicrotasks);
} else {
console.timeEnd('microtasks');
}
}
runMicrotasks();
console.time('macrotasks');
count = 0;
function runMacrotasks() {
if (count++ < 10000) {
setTimeout(runMacrotasks, 0);
} else {
console.timeEnd('macrotasks');
}
}
runMacrotasks();
}
混合使用的复杂场景
当宏任务和微任务混合使用时,执行顺序可能变得复杂:
setTimeout(() => console.log('timeout1'), 0);
setTimeout(() => {
console.log('timeout2');
Promise.resolve().then(() => console.log('promise inside timeout'));
}, 0);
Promise.resolve().then(() => {
console.log('promise1');
setTimeout(() => console.log('timeout inside promise'), 0);
});
process.nextTick(() => {
console.log('nextTick1');
process.nextTick(() => console.log('nextTick inside nextTick'));
});
不同环境下的行为差异
浏览器和Node.js在处理微任务时存在差异:
// 浏览器中的表现
button.addEventListener('click', () => {
Promise.resolve().then(() => console.log('microtask'));
console.log('listener');
});
// Node.js中的http模块
const server = require('http').createServer();
server.on('request', (req, res) => {
Promise.resolve().then(() => console.log('microtask'));
res.end('hello');
console.log('request');
});
调试技巧
使用调试工具可以更好地理解任务执行顺序:
// 添加标签以便在调试工具中识别
queueMicrotask(() => {
console.log('labeled microtask');
});
// 使用async/await
async function debugAsync() {
console.log('before await');
await Promise.resolve();
console.log('after await');
}
最佳实践建议
- 避免在微任务中执行耗时操作
- 谨慎使用递归的process.nextTick
- 对于大量异步操作,考虑使用setImmediate而非setTimeout
- 理解不同环境下的事件循环差异
// 良好的任务分割
function processChunk(data, callback) {
let index = 0;
function next() {
if (index < data.length) {
// 处理一部分数据
const chunk = data.slice(index, index + 100);
index += 100;
// 使用setImmediate避免阻塞
setImmediate(() => {
processChunkSync(chunk);
next();
});
} else {
callback();
}
}
next();
}
上一篇: 事件循环的阶段划分
下一篇: process.nextTick详解