同样的代码,在浏览器中跟在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:执行setTimeout和setInterval中到期的回调函数
pedding callback:上一轮循环中少数的I/O callback会被延迟到这里执行,比如系统调用错误,如网络通信的错误回调
idle,prepare:仅内部调用
poll:获取新的I/O 事件,执行I/O callback,在适当的条件下会阻塞在这个阶段
check:执行setImmediate的 callback
close callback:执行close的事件回调
解释一下 timers , poll 和 check 。
当执行 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… ,我也是这两篇文章的受益者,希望你也能有所收获。