-
先抛一块砖
setTimeout(() => console.log(1)); setImmediate(() => console.log(2)); process.nextTick(() => console.log(3)); Promise.resolve().then(() => console.log(4)); (() => console.log(5))();执行结果:
5 3 4 1 2
node事件分 同步、异步,由于JS的单线程原理,异步事件的处理方式依靠 事件循环。
- 异步事件分为:微事件/宏事件
- 微事件包括:
- process.nextTick
- Promise.then
- 宏事件包括:
- setTimeout/setInterval
- setImmediate
- I/O callback
不同的异步事件,循环规则也不同。
宏事件循环
- timers阶段:setTimeout/setInterval的回调函数执行阶段
- I/O callbacks阶段:未解决的回调执行执行
- poll阶段:轮询阶段。接受新的I/O回调,并将其加入轮询列队中进行先进先出的顺序执行。
- 若该阶段事件列队为空,检查
check阶段是否有事件列队,有则顺序执行。同时也会检查是否有定时器到达阈值,如果有则循环绕到timers阶段时执行。如果没有其他异步任务要处理会一直停留在该阶段等待 I/O 结果返回。 - 若该阶段的事件列队不为空,则将事件列队中的事件执行完直到为空,或直到内存溢出
- 若该阶段事件列队为空,检查
- check阶段:执行setImmediate()的回调函数
- close callbacks:执行关闭请求的回调函数
.on('close', callback)
微事件循环
- process.nextTick
- Promise.then
在同一次循环中process.nextTick总要比Promise.then先执行
循环原则
- 只有前一个阶段的事件队列全部清空以后,才会执行下一个阶段的事件队列
- 进入到下一个阶段的事件列队之前,会先执行微事件列队直到为空。
- 同一次循环中,微事件的执行总是优先于宏事件
事件执行顺序:
- 同步事件
- 发出异步请求
- 规划定时器生效的时间
- process.nextTick
- Promise.then
- 事件循环
与 浏览器 环境的EventLoop不同
浏览器环境的EventLoop代码解释
// 事件循环 => 主线程
while(macroQueue.waitForMessage()){
// 1. 执行完调用栈上当前的同步任务
// call stack
// 2. 遍历微任务列队,直到把微任务列队上的所有任务都执行完毕(清空微任务列队)
// 注意:微任务可以添加新的微任务到当前队列中
for(let i = 0; i<microQueue.length; i++){
// 获取并执行下一个微任务(先进先出)
microQueue[i].processNextMessage()
}
// 3. 渲染(渲染线程)
// 4. 从宏任务中取出下一个宏任务,继续循环
macroQueue.processNextMessage();
}
主要区别在于微任务列队的执行
- 浏览器环境的EventLoop会在每一个 宏事件 执行完成之后,遍历微任务列队中的所有微任务进行执行
- node环境的EventLoop则是在 每个阶段中的列队 的所有事件执行完成之后,并且在进入到下一个阶段之前 执行微任务列队中的微任务
微任务的作用
- 减少操作延迟
- 使代码在 当前宏任务同步代码后 及 下一个宏任务前 执行
- 可实现批量操作,可以看下面这个有趣的栗子🌰
function setup(){
let messageQueue = [];
return (message) => {
messageQueue.push(message);
// 加上 messageQueue.length === 1 的判断是为了queueMicrotask() 只被执行一次
if (messageQueue.length === 1) {
// queueMicrotask 将在当前宏任务下 callStack执行栈中的同步任务执行结束后 才开始执行
queueMicrotask(() => {
const json = JSON.stringify(messageQueue);
messageQueue.length = 0;
console.log(json);
})
}
}
}
queueMicrotask(()=>{
console.log('queueMicrotask')
})
const sendMessage = setup();
sendMessage("刘备")
sendMessage("关羽")
sendMessage("曹操")
// 至此当前callStack调用栈中的同步任务执行完毕,将执行 queueMicrotask 微任务
// setTimeout将放到下一个宏任务进行执行
setTimeout(() => {
sendMessage("诸葛亮")
});
终极挑战
setTimeout(() => {
process.nextTick(() => {
console.log(1)
process.nextTick(() => console.log(2))
});
console.log(3)
setTimeout(() => { console.log(4)})
setImmediate(() => console.log(5));
});
setTimeout(() => {
process.nextTick(() => console.log(6));
Promise.resolve(7).then(res=>console.log(res))
console.log(8)
});
(() => console.log(9))();
setImmediate(() => console.log(10));
你答对了吗?
9
3
1
2
8
6
7
10
5
4