setTimeout
当我们执行setTimeout时,node会创建一个Timeout对象来存储
setTimeout
Timeout具体属性见下
Timeout
其中_idleStart很重要,是指这个定时器的起始时间,比如在10秒的时候设置了一个40秒的定时器,那么到期的时候就检查这个now - _idleStart 是否大于定时的40秒,而这个时间应当是程序启动后经过的毫秒数。(纯属个人猜测)
生成了定时器对象后,怎么组织管理就是个问题了,定时器在node中是以对象加链表来组织的,相同时间的定时器会被放到同一个链表中,如都是定时的40毫秒,但设置的时间不同,那么他们就会被放到同一个list中,见下图
// ╔════ > Object Map
// ║
// ╠══
// ║ refedLists: { '40': { }, '320': { etc } } (keys of millisecond duration)
// ╚══ ┌─────────┘
// │
// ╔══ │
// ║ TimersList { _idleNext: { }, _idlePrev: (self), _timer: (TimerWrap) }
// ║ ┌────────────────┘
// ║ ╔══ │ ^
// ║ ║ { _idleNext: { }, _idlePrev: { }, _onTimeout: (callback) }
// ║ ║ ┌───────────┘
// ║ ║ │ ^
// ║ ║ { _idleNext: { etc }, _idlePrev: { }, _onTimeout: (callback) }
// ╠══ ╠══
// ║ ║
// ║ ╚════ > Actual JavaScript timeouts
// ║
// ╚════ > Linked List
插入的流程如下
insert
初始化TimerList的如下
timer list
在初始化一个TimersList时就会以他所属的过期的时间设置一个libuv的定时器,到期后处理自己这个list中node定时器,若是,还有未到期的,那么就继续设置libuv的定时器
接下来就是定时器到期后怎么处理了
timer outdate
上面我的截图里说的语句不通了激动了,从上面可以看到,我们新设置的相同的定时器尤其是针对setTimeout(0)(虽然我们不可能有零这种情况),其实我想说的是,假设我们设置了两个1msecs的定时器,见代码吧
setTimeout(() => { // 1
console.log(1);
setTimeout(() => { // 2
console.log(2);
});
setImmediate(() => { // 3
console.log(3);
})
})
上述代码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过期)了,即使是下面的代码也不行
setTimeout(() => { // 1
console.log(1);
setTimeout(() => { // 2
console.log(2);
});
for(let i = 0; i < 100e100; i ++) { // 1msec的时间绝对有了,那么是不是在本轮loop就可以判断timer过期呢,不会
let c = 0;
}
setImmediate(() => { // 3
console.log(3);
})
})
定时器的整体组织方式就是为了方便管理,减少底层真实定时器的使用。
setImmediate
那么setImmediate内设置setImmediate呢?
看代码吧,immediate是通过一个全局的list来管理的
immediate
list
调用setImmediate
Immediate
Immediate构造函数的处理
append Immediate
加下来就是处理 check阶段处理immediate了
才处理immediate
list
上图解释的很清楚了,这就告诉了我们另一个问题,在同一时期设置的setImmeidate会放到同一个队列,并且在一次loop check阶段就把所有的immediate回调给执行了。
setTimeout(() => { // 1
console.log(1);
setTimeout(() => { // 2
console.log(2);
});
for(let i = 0; i < 10e10; i++) {
let c = 0;
} // 上面的那个肯定过期了
for(let i = 0; i < 100; i++>) {
setImmediate(() => { // 3
console.log(3);
})
}
})
上面的代码我们的2在1的回调执行之后一定是过期的,那么若是同一时间设置的setImmediate不会在同一个loop的check阶段那么,我们的2输出之后就一定会有3,可以执行一下是没有的,也就证明了我们上面的源码分析是正确的。
最终总结
打完收工,源码阅读很考验啊,应当先知道代码的最终功能是什么?你想要知道的问题是什么?然后再去跟代码,一定要先找到函数入口,在就是一步一步调试是很有用的,等着看看怎么调试v8以及node的c++代码势要把microtasks也高明白了。