与浏览器环境有何不同
node环境和浏览器环境,表现出来的事件循环状态,大体表现一致 唯一不同的是:
- JS引擎存在 monitoring process 进程,会持续不断的检查主线程执行为空,一旦为空,就会去 callback queue 中检查是否有等待被调用的函数。(只有宏任务和微任务两个队列)
- node 中是依靠 libuv 引擎实现,我们书写的 js 代码有 V8 引擎分析后去调用对应的 nodeAPI ,这些 api 最后由 libuv 引擎驱动,在 libuv 引擎中有一套自己的模型,把不同的事件放在不同的队列中等待主线程执行。( 模型中有6种宏任务队列和1种微任务队列 )
Node事件循环的几个阶段
// libuv引擎中的事件模型,在每个模型后面都添加了一些说明
┌───────────────────────────────────────────────────────┐
┌─>│ timers │ setTimeout/setInterval的回调
│ └──────────┬────────────────────────────────────────────┘
│ ↓
│ ┌──────────┴────────────────────────────────────────────┐
│ │ pending callbacks │ 处理网络、流、tcp的错误回调
│ └──────────┬────────────────────────────────────────────┘
│ ↓
│ ┌──────────┴────────────────────────────────────────────┐
│ │ idle, prepare │ 只在node内部使用
│ └──────────┬────────────────────────────────────────────┘
│ ↓ ┌───────────────┐
│ ┌──────────┴────────────────────────────────────────────┐ │ incoming: │
│ │ poll │ 执行poll中的i/o队列,检查定时器是否到时 <------│ connections,
│ └──────────┬────────────────────────────────────────────┘ │ data, etc. │
│ ↓ └───────────────┘
│ ┌──────────┴────────────────────────────────────────────┐
│ │ check │ 存放setImmediate回调
│ └──────────┬────────────────────────────────────────────┘
│ ↓
│ ┌──────────┴────────────────────────────────────────────┐
└──┤ close callbacks │ 关闭的回调(socket.on('close')...)
└───────────────────────────────────────────────────────┘
Node事件循环中的几个阶段
官方的event-loop-timers-and-nexttick更详细的说明
- times:这个阶段执行定时器队列中的回调函数 (
setTimeout
和setInterval
) - pending callback:这个阶段执行几乎所有的回调( 网络、流、tcp错误... )。除了,close 回调、定时器回调、
setImmediate
回调这3个规定好的阶段 - idle,prepare:这个阶段仅在内部使用( 可以暂不理会 )
- poll:等待新的I/O事件,node在特殊情况下会阻塞这里,检查定时器是否到时( 入口 )
- check:
setImmediate()
的回调会在这个阶段执行 - close callbacks:例如
socket.on('close', ...)
process.nextTick
和.then()
会在事件循环的阶段切换过程中执行
说了一堆概念,来一起看看下面这段代码
(function test() {
setTimeout(function () { console.log(4) }, 0);
new Promise(function (resolve, reject) {
console.log(1);
for (var i = 0; i < 10000; i++) {
i == 9999 && resolve();
}
console.log(2);
}).then(function () {
console.log(5);
});
console.log(3);
})();
// 这段代码是不是很熟悉
// 最终结果1,2,3,5,4 和 浏览器中效果一致
来点稍微高难度的
和上篇博客 从一道执行题,了解浏览器中JS执行机制 中的代码一样 (⊙﹏⊙)b
console.log(1)
setTimeout(() => {
console.log(2)
new Promise(resolve => {
console.log(4)
resolve()
}).then(() => {
console.log(5)
})
})
new Promise(resolve => {
console.log(7)
resolve()
}).then(() => {
console.log(8)
})
setTimeout(() => {
console.log(9)
new Promise(resolve => {
console.log(11)
resolve()
}).then(() => {
console.log(12)
})
})
// 浏览器中的结果:1、7、8、2、4 , 5、9、11、12
// Node 中的结果:1、7、8、2、4 , 9、11、5、12
解析如下:
- 在浏览器中
macro task
执行完成后,再次循环 宏任务 的回调队列之前,会优先处理micro中的任务。因此结果是: 1、7、8、2、4、5、9、11、12- 在
Node
中有6个宏任务队列,事件循环首先进入 poll 阶段。进入 poll 阶段后查看是否有设定的 timers ( 定时器 )时间到达,如果有一个或多个时间到达, Event Loop 将会跳过正常的循环流程,直接从 timers 阶段执行,并执行 timers 回调队列,此时只有把 timers 阶段的回调队列执行完毕后。才会走下一个阶段,这也就是为什么setTimeout
中有.then
,而没有被立即执行的原因,当 timers 阶段的回调队列执行完毕后,切换到下一个阶段这个过程中去触发 微任务(process.nextTick
和.then
) 。在阶段与阶段的切换之间。
再来一道基础题
setTimeout(function () {
console.log('setTimeout')
});
setImmediate(function () {
console.log('setImmediate')
});
执行结果:(
setTimeout、setImmediate
) 或 (setImmediate、setTimeout
)
为什么?
setTimeout
在标准中默认的最小时间是4ms,如果开启node和执行node代码的时间小于4ms,那么代码解析完成后传入libuv
引擎,首先会进入 poll 阶段,此时查看设定的时间是否达到截止时间点,如果这个时间小于4ms( 没有达到 ),那么会走 check 阶段,会触发setImmediate
再触发setTimeout
。如果开启node和执行node代码时间大于等于4ms,那么就会先执行setTimeout
后执行setImmediate
在基础上进化的经典题
setImmediate(() => {
console.log('setImmediate1')
setTimeout(() => {
console.log('setTimeout1')
}, 0);
})
setTimeout(()=>{
process.nextTick(()=>console.log('nextTick'))
console.log('setTimeout2')
setImmediate(()=>{
console.log('setImmediate2')
})
},0);
两种情况 ( nextTick执行的位置:是在队列切换时执行 )
- 如果
setImmediate
先执行:setImmediate1、setTimeout2、setTimeout1、nextTick、setImmediate2
- 如果
setTimeout
先执行:setTimeout2、nextTick、setImmediate1、setImmediate2、setTimeout1
setImmediate和process.nextTick的直译
- Immediate立即执行的意思,其实际上是固定在
check
阶段才会被执行。这个直译的意义和process.nextTick
才是最匹配的。- node的开发者们也清楚这两个方法的命名上存在一定的混淆,他们表示不会把这两个方法的名字调换过来---因为有大量的node程序使用着这两个方法,调换命名所带来的好处与它的影响相比不值一提。
了解这些东西有什么用?
- 可以使我们对异步代码的执行顺序有清晰的认知( 重要的 )
- 推迟任务执行
- 面试
总结
这些概念远比想象中的要重要
- 为什么
new Promise
第一个参数是同步执行的 ?学习Promise && 简易实现Promise浏览器
中的 JS 执行机制是什么样子的?从一道执行题,了解浏览器中JS执行机制
附:这篇博客 也许 想表达 概念远比想象中的要重要 (⊙﹏⊙)b