//lib/internal/timers.js~line#34
after *= 1; // coalesce to number or NaN
if (!(after >= 1 && after <= TIMEOUT_MAX)) {
if (after > TIMEOUT_MAX) {
process.emitWarning(${after} does not fit into +
' a 32-bit signed integer.' +
'\nTimeout duration was set to 1.',
'TimeoutOverflowWarning');
}
after = 1; // schedule on next tick, follows browser behavior
}
先来温习一下event loop中的几个phase可参见我的上篇文章libuv概览
为什么会强调上述几个阶段呢?可以参见一下node官网对于event loop的解释
其实我们可以总解一下js代码的执行时间有哪些?
接下来就是解析一下他们的执行顺序问题
setTimeout(n) VS setImmediate
// 假设我的t 是2,那么输出就是
setTimeout
setImmediate
//lib/internal/timers.js~line#34 after *= 1; // coalesce to number or NaN if (!(after >= 1 && after <= TIMEOUT_MAX)) { if (after > TIMEOUT_MAX) { process.emitWarning(
${after} does not fit into
+ ' a 32-bit signed integer.' + '\nTimeout duration was set to 1.', 'TimeoutOverflowWarning'); } after = 1; // schedule on next tick, follows browser behavior }setTimeout(() => { // 1 console.log('外层timeout'); setTimeout(() => { // 2 console.log('set timeout in timeout'); }); setImmediate(() => { // 3 console.log('set immediate in timeout'); }); });
const fs = require('fs');
fs.readFile('./test.js', () => { setTimeout(() => { console.log('set timeout in poll phase'); }); setImmediate(() => { console.log('set immediate in poll phase'); }); });
setImmediate(() => { console.log('immediate'); }); function a() { process.nextTick(() => { console.log('set nextTick'); a(); }); } a();
const promise = Promise.resolve(234)
setImmediate(() => { console.log('immediate'); });
testPromise();
function testPromise() { promise = promise.then(() => { console.log('promise'); testPromise(); }); }
总结一下
最后再来点儿,对于setTimeout和setImmediate的代码分析,来具体解释为什么4成立
setTimeout 当我们执行setTimeout时,node会创建一个Timeout对象来存储 {% asset_img timeout.png setTimeout %}
Timeout具体属性见下
{% asset_img timeoutobj.png Timeout %}
其中_idleStart很重要,是指这个定时器的起始时间,比如在10秒的时候设置了一个40秒的定时器,那么到期的时候就检查这个now - _idleStart 是否大于定时的40秒,而这个时间应当是程序启动后经过的毫秒数。(纯属个人猜测) 生成了定时器对象后,怎么组织管理就是个问题了,定时器在node中是以对象加链表来组织的,相同时间的定时器会被放到同一个链表中,如都是定时的40毫秒,但设置的时间不同,那么他们就会被放到同一个list中,见下图
插入的流程如下 {% asset_img insert.png insert %} 初始化TimerList的如下 {% asset_img timerlist.png timer list %} 在初始化一个TimersList时就会以他所属的过期的时间设置一个libuv的定时器,到期后处理自己这个list中node定时器,若是,还有未到期的,那么就继续设置libuv的定时器 接下来就是定时器到期后怎么处理了 {% asset_img timeouecb.png timer outdate %} 上面我的截图里说的语句不通了激动了,从上面可以看到,我们新设置的相同的定时器尤其是针对setTimeout(0)(虽然我们不可能有零这种情况),其实我想说的是,假设我们设置了两个1msecs的定时器,见代码吧
上述代码1,2的过期时间都是1,所以他们在同一个timerList中,还有我们看到前面的描述,当我们是while去处理timerlist的,根据前面的讲述我们知道3一定比2先输出,但是我们是while处理timerlist的,为什么没有判断2过期呢,我就发现了,因为判断过期取的是now - timer._idleStart, 而这个now是在定时器cb执之前取的,而timer._idleStart是在setTimeout时设置的,那就意味着,2的_idleStart 一定比1的到期是去的now 大,那就很明显了,无论如何都是是无法判断2过期(在本次loop期间判断2过期)了,即使是下面的代码也不行
定时器的整体组织方式就是为了方便管理,减少底层真实定时器的使用。
setImmediate 那么setImmediate内设置setImmediate呢? 看代码吧,immediate是通过一个全局的list来管理的 {% asset_img immediatelist.png immediate list %}
调用setImmediate {% asset_img newimme.png Immediate %}
Immediate构造函数的处理
{% asset_img appendimme.png append Immediate %}
加下来就是处理 check阶段处理immediate了
{% asset_img processimme.png 才处理immediate list %}
上图解释的很清楚了,这就告诉了我们另一个问题,在同一时期设置的setImmeidate会放到同一个队列,并且在一次loop check阶段就把所有的immediate回调给执行了。
上面的代码我们的2在1的回调执行之后一定是过期的,那么若是同一时间设置的setImmediate不会在同一个loop的check阶段那么,我们的2输出之后就一定会有3,可以执行一下是没有的,也就证明了我们上面的源码分析是正确的。
最终总结
打完收工,源码阅读很考验啊,应当先知道代码的最终功能是什么?你想要知道的问题是什么?然后再去跟代码,一定要先找到函数入口,在就是一步一步调试是很有用的,等着看看怎么调试v8以及node的c++代码势要把microtasks也高明白了。
参考