解惑Event Loop在浏览器,Node中的不同

222 阅读4分钟

同样的代码,在浏览器中跟在node中表现却是不一样的,请看:

setTimeout(() => {
    console.log('setTimeout 1');
    Promise.resolve().then(() => {
        console.log('promise 1')
    })
}, 0);

setTimeout(() => {
    console.log('setTimeout 2');
    Promise.resolve().then(() => {
        console.log('promise 2')
    })
}, 0);

在浏览器中的结果:

setTimeout 1
promise 1
setTimeout 2
promise 2

在node(8.10.0)中:

setTimeout 1
setTimeout 2
promise 1
promise 2

由此可以看出,node的Event Loop跟浏览器中的不一样(在11版本的node结果已经趋同于浏览器了),在对于 setTimeout 内的微任务执行不一样。

为什么呢?

浏览器中先是执行同步代码,然后到 setTimeout setInterval 等,取出一个宏任务,执行之后,会检索微任务队列是否有微任务,如果有,那就全执行,结束后再执行下一个宏任务,执行完后,再检索微任务,如果不为空,则执行,以此循环。

所以。第一个 setTimeout 运行后,会执行里面的微任务,然后才到第二个 setTimeout 运行,然后执行第二个里面的微任务。

这只是一道题的解答,根本的解决思路,应该从这道题中,去思考浏览器的Event Loop。

浏览器为什么需要Event Loop?

浏览器,是多线程的,但一个网页页面的各种行为是单线程的。

为了协调事件,用户交互,脚本,渲染,网络等,用户代理必须要用事件轮询(Event Loop)。

浏览器的Event Loop

浏览器的任务分为宏任务(macroTask)和微任务(microTask)。

宏任务,即script,setTimeout,setInterval,setIemmediate,I/O,UI渲染

微任务,process.nextTick,promise.then,MuationObserver

他们的执行顺序是这样的:

先执行同步代码,然后到检索是否有宏任务,如果有,则取出来执行,执行完毕这个宏任务,检索是否有微任务,如果有,则执行微任务,执行完毕,再检索是否存在宏任务,直到宏任务队列为空,微任务队列也为空。

同步任务 -> 宏任务 -> 微任务

所以在上面代码中,当第一个 setTimeout 执行完的时候,微任务中存在第一个 promise ,所以会先执行这个微任务,然后才到第二个宏任务 setTimeout

Node中的Event Loop

Node中的Event Loop比较复杂。

Node中的 Event Loop 有几个阶段:

timers :执行 setTimeoutsetInterval 中到期的回调函数

pedding callback :上一轮循环中少数的 I/O callback 会被延迟到这里执行,比如系统调用错误,如网络通信的错误回调

idleprepare :仅内部调用

poll :获取新的I/O 事件,执行 I/O callback ,在适当的条件下会阻塞在这个阶段

check :执行 setImmediate 的 callback

close callback :执行 close 的事件回调

解释一下 timers , pollcheck

当执行 timer 阶段的时候,如果回调函数中又注册了一个 setTimeout 的事件,那么这个事件将会在下一轮中被回调,因为 timer 阶段已经在执行了。

poll 阶段做了两件事,一是清空 poll 回调函数的队列,二是检查是否有 setImmediate ,如果有,则进入 check 阶段,如果没有则会阻塞在这个阶段。但是这个阶段有一个对 timers 队列的检查机制(不保证靠谱),如果队列非空,则进入下一轮Event Loop。

check 阶段就执行 setImmediate 事件。

那么Node的Event Loop跟浏览器不同之处在于:MicroTask的调用时机不同

浏览器的调用时机:

在每一宏任务执行完之后,会清空微任务的队列,也就是说,时机是在于每一个宏任务结束时

Node的调用时机:

每一个阶段都会清空微任务

配合着例子说明:


    // 浏览器的Event Loop结果
    // 执行宏任务
    setTimeout 1
    // 宏任务执行完毕,执行MicroTask
    promise 1
    // MicroTask执行完毕,执行下一个宏任务
    setTimeout 2
    // 宏任务执行完毕,执行MicroTask
    promise 2
    // MicroTask执行完毕,执行下一个宏任务
    
    // Node 的Event Loop结果
    // 执行timer
    setTimeout 1
    setTimeout 2
    // timer 执行完毕,执行 MicroTask
    promise 1
    promise 2
    // MicroTask 执行完毕,执行下一个阶段...

如果觉得讲的不是很详细,请移步 segmentfault.com/a/119000001…lynnelv.github.io/js-event-lo… ,我也是这两篇文章的受益者,希望你也能有所收获。