详解Nodejs的timers模块(2)

上面的源码,就是在timers模块中,内部的一个私有构造函数,在timers公开的一些方法,占据了一个很重要的位子,因为,这个方法,是timers模块,与C++代码链接的重要部分。该部分,是没有示例可以给出的,只有在后面使用timers模块对外公开的API中,来看下对应的使用效果。

这里之所以,要先把这个构造函数放在这里,因为,在我看来,如果能先对这个构造函数有所了解的话,那么接下来看timers模块中的其他方法时,就会变的简单很多。

当然,也有可能是,因为没有看其他的源代码,而导致对于该构造函数的一些方法和属性,很没用感觉的,那么,接下来,就继续看下去吧。

timers模块的源码

timers中的源码,可以分为两部分,在这里,只会看下其中的一部分,还有另外一部分,是和延时执行相反的立即执行的回调函数,这是我们不常用到的,所以这里就不在占用篇幅。

这里,依然使用源码来开始:

'use strict'; // timer_wrap模块,为底层C++实现的模块 var Timer = process.binding('timer_wrap').Timer; // Timer在控制台打印出的数据如下: // {[Function: Timer] 是一个构造函数 // kOnTimeout: 0, // now: [Function: now] // } // Nodejs模拟的双向链表的操作模块,请查看前一篇关于linklist的文章 var L = require('_linklist'); // 断言的管理模块中的ok方法 var assert = require('assert').ok; var kOnTimeout = Timer.kOnTimeout | 0; // Timeout values > TIMEOUT_MAX are set to 1. var TIMEOUT_MAX = 2147483647; // 2^31-1 // 把timer添加到debug的模块中,并生成一个函数,命名为debug // 在之后,直接调用,该函数,即可把官员timer的错误信息,打印到控制台 var util = require('util'); var debug = util.debuglog('timer'); // 注,debuglog方法,应该是最近的版本中,新添加的,因为在一年前,刚接触nodejs时, // util模块中,还没有该方法 // Object containing all lists, timers // key = time in milliseconds // value = list var lists = {}; // the main function - creates lists on demand and the watchers associated // with them. // 把item存入到一个链表中去,并且把msecs对应的链表,存入到lists对象中去 // lists的格式是这样的: // { // "1000": 这里是一个循环链表,该链表内,包含了所有msecs=1000的list对象 // "2000":{} // } function insert(item, msecs) { // 给item定义两个私有属性 // 一个记录当前时间 item._idleStart = Timer.now(); // 一个记录毫秒时间,类似于过期时间 item._idleTimeout = msecs; // 如果定义的毫秒,是负值,则直接返回,不做后面的处理 if (msecs < 0) return; var list; // 如果该过期时间,已经缓存在了lists对象中,则直接找到缓存的数据 if (lists[msecs]) { list = lists[msecs]; } else { // 否则,执行新建一个list数据 // 并把item和msecs的数据初始化到新创建的对象中去 list = new Timer(); // 下面这些,就是Timer实例化之后,包含的方法 // close // ref // unref // start // stop // setRepeat // getRepeat // again // 实例化之后,调用start方法 list.start(msecs, 0); // 把list对象,改为一个循环链表 L.init(list); // 把该list添加到lists对象中缓存 // 并设置一些属性,这些属性,在其他方法中被用到 lists[msecs] = list; list.msecs = msecs; list[kOnTimeout] = listOnTimeout; } // 把item插入到list的下一个节点去 L.append(list, item); assert(!L.isEmpty(list)); // list is not empty } // 每一个list的kOnTimeout的属性值,应该是一个回调函数 // 所以,其内部指向的是list本事 function listOnTimeout() { var msecs = this.msecs; var list = this; debug('timeout callback %d', msecs); // 类似一个时间戳,但是又和Date.now()的毫秒级时间戳不同,不知道是如何判断这个的 var now = Timer.now(); debug('now: %d', now); var diff, first, threw; // 当时间到了之后,把对应该时间的链表中的所有元素执行 // 如果出现异味,则等一会再次执行,请看源码中的具体注释 while (first = L.peek(list)) { // If the previous iteration caused a timer to be added, // update the value of "now" so that timing computations are // done correctly. See test/simple/test-timers-blocking-callback.js // for more information. // 本处的while是,把list的所有前置列表,都处理一遍,直到list所处的链表中,只有list时结束 if (now < first._idleStart) { // 当first元素,当执行insert时,会操作_idleStart的属性值 // 如果Timer.now的值,是一直增加的,那么这里为神马会执行? // 那么又为什么要有这个判断?只是打了一个log,难道只是为了做个通知? now = Timer.now(); debug('now: %d', now); } // 求这个差值?并且与list的msecs值进行判断 diff = now - first._idleStart; if (diff < msecs) { // 执行到这里,那边把list继续延时一段时间,因为当前的一个item没有被执行 // 所以重新计时,再执行一次 list.start(msecs - diff, 0); debug('%d list wait because diff is %d', msecs, diff); // 并且直接return,结束本回调函数,等待msecs-diff时间之后,再次执行 return; } else { // 把first从它所在的链表中移除 L.remove(first); // 我觉得,这里是在判断,是否移除成功 assert(first !== L.peek(list)); // 如果当前的first没有回调函数,那么不需要再向下执行,继续while循环 if (!first._onTimeout) continue; // 接下来,就是执行回调的处理了,处理的逻辑还行,看起来不算复杂 // 只是,有些判断,我现在无法理解到,为什么要这么判断 // v0.4 compatibility: if the timer callback throws and the // domain or uncaughtException handler ignore the exception, // other timers that expire on this tick should still run. // // https://github.com/joyent/node/issues/2631 var domain = first.domain; if (domain && domain._disposed) continue; try { if (domain) domain.enter(); threw = true; first._onTimeout(); if (domain) domain.exit(); threw = false; } finally { if (threw) { // We need to continue processing after domain error handling // is complete, but not by using whatever domain was left over // when the timeout threw its exception. var oldDomain = process.domain; process.domain = null; process.nextTick(function() { list[kOnTimeout](); }); process.domain = oldDomain; } } } } debug('%d list empty', msecs); assert(L.isEmpty(list)); list.close(); delete lists[msecs]; } var unenroll = exports.unenroll = function(item) { L.remove(item); // _idleTimeout中保存着msecs的值, // 所有可以根据该属性,直接找到该对象在lists中的缓存数据 // 不过,item的msecs中,也保存了list本身的msecs的 var list = lists[item._idleTimeout]; // if empty then stop the watcher debug('unenroll'); if (list && L.isEmpty(list)) { debug('unenroll: list empty'); // list调用C++的接口 list.close(); delete lists[item._idleTimeout]; } // if active is called later, then we want to make sure not to insert again item._idleTimeout = -1; // 本方法,其实就是在清理一些默认的数据了 // 属于,当一个方法执行完之后,把其对应的数据,都直接清理掉 }; // Does not start the time, just sets up the members needed. exports.enroll = function(item, msecs) { // 给item重新设置一些属性 // msecs的值,需要时number类型,并且有效的正整数和零 if (!util.isNumber(msecs)) { throw new TypeError('msecs must be a number'); } if (msecs < 0 || !isFinite(msecs)) { throw new RangeError('msecs must be a non-negative finite number'); } // if this item was already in a list somewhere // then we should unenroll it from that // 保证,item不会存在于两个链表中, // 比如,我最初把item设置为1000之后执行,那么item在lists[1000]所在的链表中 // 接下来,我又把item设置为2000之后执行,那么就要先吧item从原来的lists[1000]的链表中删除 // 然后,添加到lists[2000]所指向的链表去 if (item._idleNext) unenroll(item); // Ensure that msecs fits into signed int32 // 保证是在最大值之内的,否则,设置为一个系统设置的最大值 if (msecs > TIMEOUT_MAX) { msecs = TIMEOUT_MAX; } // 设置信息,并初始化item本身的链表 item._idleTimeout = msecs; L.init(item); }; // call this whenever the item is active (not idle) // it will reset its timeout. exports.active = function(item) { // 把item插入到缓存的lists对象中, // 或者把已经存在的于对象中的item,进行一次数据更新 var msecs = item._idleTimeout; if (msecs >= 0) { // 看上面的函数,enroll可以知道,msecs是必须大于等于0的 var list = lists[msecs]; // 如果list存在于lists中,找到对应的链表 if (!list || L.isEmpty(list)) { // 如果list为空,或者list为空链接,则执行insert方法,创建一个新的链表 // 并且把该链表,保存到lists[msecs]中去 insert(item, msecs); } else { // 如果有,那么更新item属性的当前时间,把item插入到list链表中去 item._idleStart = Timer.now(); L.append(list, item); } } }; /* * DOM-style timers */ exports.setTimeout = function(callback, after) { // setTimeout的实现源代码 // 前两个参数必须是固定的 var timer; // after转化为数字,或者NaN after *= 1; // coalesce to number or NaN // 如果不在合法范围之内,则把after设置为1 if (!(after >= 1 && after <= TIMEOUT_MAX)) { after = 1; // schedule on next tick, follows browser behaviour } // 根据Timerout构造函数,生成一个实例,该构造函数完成的功能 // 只是创建一个对象,设置了一些属性和一些方法 timer = new Timeout(after); // 实例化后的timer包含以下内部属性 // _idleTimeout = after; // _idlePrev = this; // _idleNext = this; // _idleStart = null; // _onTimeout = null; // _repeat = false; // 以及一下几个原型链方法 // unref // ref // close // 如果传入的参数,小于等于2个,说明没有多余的默认参数传入 if (arguments.length <= 2) { timer._onTimeout = callback; } else { // 如果有多余的默认参数传入,那么就要把多余的参数缓存一下 // 使用闭包,重新设置一个回调函数 var args = Array.prototype.slice.call(arguments, 2); timer._onTimeout = function() { callback.apply(timer, args); } } // 设置timer的domain属性为process的domain属性 // 该属性,暂时还不知道为什么存在 if (process.domain) timer.domain = process.domain; // 把timer设置为启动,并在active中,插入到等待执行的列表中去 exports.active(timer); // 返回Timeout的实例对象,所以,可以想象setTimeout的返回值,到底有哪些属性和方法了吧 return timer; }; exports.clearTimeout = function(timer) { // 只有timer存在 // 回调存在,回调时间存在的情况下,才需要把该方法清理掉 // 至于为什么要判断这些条件,请参考listOnTimeout方法内部的注释及逻辑 if (timer && (timer[kOnTimeout] || timer._onTimeout)) { timer[kOnTimeout] = timer._onTimeout = null; // 清除回调,时间等属性,然后把timer自lists链表中,去除掉 // 这样减少在每次调用时,对lists中对象的无意义的循环 if (timer instanceof Timeout) { timer.close(); // for after === 0 } else { exports.unenroll(timer); } } }; exports.setInterval = function(callback, repeat) { // 前期的处理,和setTimeout方法相同,唯一不同的是,回调 // 在本方法中,回调之后,再添加另外一个计时器 // 在我看来,就像是每次去调用setTimeout方法一样 repeat *= 1; // coalesce to number or NaN if (!(repeat >= 1 && repeat <= TIMEOUT_MAX)) { repeat = 1; // schedule on next tick, follows browser behaviour } var timer = new Timeout(repeat); var args = Array.prototype.slice.call(arguments, 2); timer._onTimeout = wrapper; timer._repeat = true; if (process.domain) timer.domain = process.domain; exports.active(timer); return timer; function wrapper() { callback.apply(this, args); // If callback called clearInterval(). if (timer._repeat === false) return; // If timer is unref'd (or was - it's permanently removed from the list.) // 下面的处理,是因为在net模块中,和在本模块中,重新启用一个计时器的方法有区别 if (this._handle) { // 该分支处理,应该是为了net模块中做的处理 // 在本模块中,暂时是没有提及到该属性的 this._handle.start(repeat, 0); } else { // 当前的模块中的回调函数 timer._idleTimeout = repeat; exports.active(timer); } } }; exports.clearInterval = function(timer) { // 基本上,就是只有timer和repeat属性存在的情况下 // 才表示timer对象,是出于Interval方法中, // 这个时候,才去清理掉repeat属性,然后clearTimeout的方法 // 清理掉该计时器 if (timer && timer._repeat) { timer._repeat = false; clearTimeout(timer); } };

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wwjsfp.html