浏览器和node下的 Event Loop
什么是Event Loop
event loop “事件循环” 可以看作是一个执行模型。他一直在查找不同的事件并执行。
它主要是正对于js的异步处理。
-
对于浏览器来说,event loop是在html5的规范中明确规定,对于不同浏览器厂商有不同的规定。
-
NodeJS的Event Loop 是基于libuv实现的。libuv对Eventloop有单独的实现
浏览器上的实现
在js中,任务队列的任务被分为:
-
MacroTask
script(整体代码)setTimeout 等
-
MicroTask
Promises 等
以上两个任务的执行顺序如下:
1. 浏览器从task队列中按照顺序取task执行
2. 检查microtask队列,若为空,执行下一个task,否则将microtask队列的任务执行完毕。
setTimeout(()=>{
console.log('timer1')
Promise.resolve().then(function() {
console.log('promise1')
})
}, 0)
setTimeout(()=>{
console.log('timer2')
Promise.resolve().then(function() {
console.log('promise2')
})
}, 0)
//浏览器执行结果
timer1
promise1
timer2
promise2
以上的实现逻辑为:
浏览器因为两个setTimeout作为两个MacroTask, 所以先输出timer1, 再执行microtask里的promise1,再输出timer2, 再执行microtask里promise2。
node 上的实现
nodejs的event loop分为6个阶段
- timers:执行setTimeout() 和 setInterval()中到期的callback。
- I/O callbacks:上一轮循环中有少数的I/Ocallback会被延迟到这一轮的这一阶段执行
- idle, prepare:队列的移动,仅内部使用
- poll:最为重要的阶段,执行I/O callback,在适当的条件下会阻塞在这个阶段
- check:执行setImmediate的callback
- close callbacks:执行close事件的callback。
以上六个阶段被分为以下两个队列:
- NodeJs的Macrotask队列:
- Timers Queue
- IO Callbacks Queue
- Check Queue
- Close Callbacks Queue
- NodeJs的microtask队列:
- Next Tick Queue:是放置process.nextTick(callback)的回调任务的
- Other Micro Queue:放置其他microtask,比如Promise等
具体的执行过程就如下:
- 执行全局Script的同步代码
- 执行microtask微任务,先执行所有Next Tick Queue中的所有任务,再执行Other Microtask Queue中的所有任务
- 开始执行macrotask宏任务,共6个阶段,从第1个阶段开始执行相应每一个阶段macrotask中的所有任务,这里是所有每个阶段宏任务队列的所有任务,在浏览器的Event Loop中是只取宏队列的第一个任务出来执行,每一个阶段的macrotask任务执行完毕后,开始执行微任务,也就是步骤2
- Timers Queue -> 步骤2 -> I/O Queue -> 步骤2 -> Check Queue -> 步骤2 -> Close Callback Queue -> 步骤2 -> Timers Queue …
setTimeout(()=>{
console.log('timer1')
Promise.resolve().then(function() {
console.log('promise1')
})
}, 0)
setTimeout(()=>{
console.log('timer2')
Promise.resolve().then(function() {
console.log('promise2')
})
}, 0)
//nodejs执行结果
timer1
timer2
promise1
promise2
//如果以上setTimeout交换位置,输出的结果也有不同
以上的逻辑为:
最初timer1和timer2就在timers阶段中。开始时首先进入timers阶段,执行timer1的回调函数,打印timer1,并将promise1.then回调放入microtask队列,同样的步骤执行timer2,打印timer2;
至此,timer阶段执行结束,event loop进入下一个阶段之前,执行microtask队列的所有任务,依次打印promise1、promise2。
总结
- 浏览器按照一个MacroTask任务,所有MicroTask的顺序运行。
- Node按照六个阶段根据不同的Macrotask队列顺序运行,并在每个阶段后面都会运行MicroTask队列