node异步队列之越学越迷糊?
发布于 3 年前 作者 zhsonga 2556 次浏览 来自 问答

开幕雷击(手动展示封面+添加弹幕) image.png 呐尼? setTimeout和setImmediate到底谁先执行? image.png 内心os: 在官网异步模型下 应该总是setTimeout先执行才对啊 或者setImmediate都immediate了应该setImmediate先执行啊 期望应该是 setTimeout1 setTimeout2 setImmediate1 setImmediate2 或着是 setImmediate1 setImmediate2 setTimeout1 setTimeout2 但是现在的 情况中出了一个问题

心中十分气愤 这官方文档有问题啊 啊这 肯定不是我的问题啊

于是准备和大佬掰头一下 image.png

准备百度,开始掰头

突然在某不知知不知明的文章中发现 timer阶段的定时器能否执行 是取决于poll阶段的 image.png

image.png 推测setImmediate先执行的情况是

推论1

1.执行代码时setTimeout和setImmediate都推入了队列 2.timer阶段检查无可执行tiemr(因为还没有经过poll阶段) 3.poll阶段处理timer可执行 4.进入check阶段,打印setImmediate 5.再次进入timer阶段 打印setTimeout

辣么 在推测一下先打印setTimeout先打印的情况 (只能先根据结果推测过程)

推论2

已知 1.tiemr要执行必须经过poll阶段 2.setTimeout(fn,0)其实是有1毫秒延迟的 因为要做到先打印setTimeout所以一定是setTimeout先进入队列,并且经过了poll阶段,并且check阶段为空

setTimeout(() => {
    console.log('setTimeout1');
});
setImmediate(() => {
    console.log('setImmediate1');
});

setTimeout和setImmediate压进对应队列 但是在进入整个异步队列的时候比较慢 此时setTimeout已经超时了(此处应该是timer阶段也可以判断定时器是否超时) 那么打印setTimeout 在打印setImmediate

似乎只有满足推论2的时候才能打印出 setTimeout1 setImmediate1 setImmediate2 setTimeout2 或者 setTimeout1 setTimeout2 setImmediate1 setImmediate2 但期望的应该是 setImmediate1 setImmediate2 setTimeout1 setTimeout2

期望示例

setTimeout(() => {
    setTimeout(() => {
        console.log('setTimeout1');
    });

    setTimeout(() => {
        console.log('setTimeout2');
    });
    setImmediate(() => {
        console.log('setImmediate1');
    });
    setImmediate(() => {
        console.log('setImmediate2');
    });
})

这这种情况下异步队列执行最外层回调时处于timer阶段 然后setTimeout1 setTimeout2进入timer队列 setImmediate1 setImmediate2 进入check队列 poll阶段处理timer check阶段执行打印setImmediate1 setImmediate2 timer阶段执行打印setTimeout1 setTimeout2 获得正确期望

理解node异步队列和浏览器异步队列区别 在看上面文章时进行测试引发的思考以及查到的下面文章 setTimeout和setImmediate

image.png

结论

1.在进入异步阶段比较慢的情况下 setTimeout可能会先超时先打印 2.timer超时在poll和timer阶段都会处理

疑问

结论2是否正确 假如机器执行较快时候也会先打印setImmediate在打印setTimeout 我自己尝试的没有这种情况 希望大佬们指点一下

3 回复

因为进入事件循环也要花费时间,所以进入的时候有可能已经过了setTimeout的执行时间,所以会先打settimeout的回调,但也可能没有过,所以先打setImmediate

setTimeout1 setImmediate1 setImmediate2 setTimeout2

上面这种情况看起来是timers阶段执行了一个,然后进入check阶段执行setImmediate,然后下一轮执行第二个setTimeout,但是不明白为啥会这样

@yuu2lee4 举例:

  • timer1._idleStart 为 1,timer1._idleTimeout 为 100
  • timer2._idleStart 为 2,timer2._idleTimeout 为 100

timer1timer2 因为 _idleTimeout 相同(都是 100)被归到同一个 List 中,然后让 libuv 在 100 毫秒后执行该 list,然而未来真正的时间,会是一个大于等于 100 毫秒的不确定的时间 FT,所以:

  • 如果 FT 是 103,那么 timer1timer2 的回调都会被执行
  • 如果 FT 是 101,那么 timer2 执行就为时尚早,因为它应该在 time >= 102 (102 = 2 + 100)的时间被执行,所以内部实现上,就会重新设置一个 timeout,在下一个事件循环中执行 timer2,因此中间的 immediate 被执行到,因为 immediate 在 check 阶段

有兴趣可以参考源码 timers.js#L514 求证

回到顶部