node Timer 模塊源碼閱讀 流水賬方式記錄timer從注冊(cè)到執(zhí)行的過程

前言

node timer 是node中一個(gè)非常重要的模塊, 幾乎所有的server相關(guān)的服務(wù)都離不開timer模塊。在node.js 基礎(chǔ)庫(kù)中,任何一個(gè)TCP I/O模塊都會(huì)產(chǎn)生一個(gè)timer(定時(shí)器)對(duì)象,以便記錄請(qǐng)求/響應(yīng)是否超時(shí)。 比如發(fā)起了一個(gè)http請(qǐng)求,請(qǐng)求頭上有個(gè)connection:keep-alive ,讓服務(wù)器維持TCP連接,但是這個(gè)連接不可能一直維持,所以會(huì)給它設(shè)置一個(gè)超時(shí)時(shí)間,一旦超時(shí)就會(huì)斷開連接,這個(gè)超時(shí)斷開操作正是通過Timers實(shí)現(xiàn)的,即使沒有keep-alive,每次請(qǐng)求的時(shí)候 也會(huì)設(shè)置一個(gè)超時(shí)時(shí)間,避免數(shù)據(jù)遲遲不返回占用server端連接。
所以可以肯定的說,只要你使用node編寫web服務(wù),一定會(huì)用到timer

定時(shí)器的實(shí)現(xiàn)

Node 中的setTimeout setInternal setImmediate API 都在 lib/timers.js中實(shí)現(xiàn)

下面我們以setTimeout為例閱讀一下timer的實(shí)現(xiàn)源碼

setTimeout定義


function setTimeout(callback, after, arg1, arg2, arg3) {

  if (typeof callback !== 'function') {

    throw new ERR_INVALID_CALLBACK();

  }

  var i, args;

  switch (arguments.length) {

    // fast cases

    case 1:

    case 2:

      break;

    case 3:

      args = [arg1];

      break;

    case 4:

      args = [arg1, arg2];

      break;

    default:

      args = [arg1, arg2, arg3];

      for (i = 5; i < arguments.length; i++) {

        // extend array dynamically, makes .apply run much faster in v6.0.0

        args[i - 2] = arguments[i];

      }

      break;

  }

  const timeout = new Timeout(callback, after, args, false, false);

  active(timeout);

  return timeout;

}

可以看到setTimeout 接收5個(gè)參數(shù),根據(jù)平常的setTimeout使用習(xí)慣,我們知道after是我們傳入的定時(shí)時(shí)間,

參數(shù)的處理我們暫時(shí)先按下不表,繼續(xù)往下看,new Timeout跟active(timeout)分別做了什么?

Timeout構(gòu)造函數(shù)


// Timer constructor function.

// The entire prototype is defined in lib/timers.js

function Timeout(callback, after, args, isRepeat, isUnrefed) {

  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

  }

  this._called = false;

  this._idleTimeout = after;

  this._idlePrev = this;

  this._idleNext = this;

  this._idleStart = null;

  // this must be set to null first to avoid function tracking

  // on the hidden class, revisit in V8 versions after 6.2

  this._onTimeout = null;

  this._onTimeout = callback;

  this._timerArgs = args;

  this._repeat = isRepeat ? after : null;

  this._destroyed = false;

  this[unrefedSymbol] = isUnrefed;

  initAsyncResource(this, 'Timeout');

}

這個(gè)Timeout 生成的timer實(shí)例 表示Node.js層面的定時(shí)器對(duì)象,比如 setTimeout、setInterval返回的對(duì)象

var timer = setTimeout(() => {}, 1000);

(劇透一下: 還有一個(gè)由底層C++ time_wrap 模塊提供的runtime層面的定時(shí)器對(duì)象:

const Timer = process.binding('timer_wrap').Timer;)

Timeout構(gòu)造函數(shù)的整個(gè)原型鏈?zhǔn)窃趌ib/timers.js 中定義的,如下述代碼所示


Timeout.prototype.unref = function() {

  if (this._handle) {

    this._handle.unref();

  } else if (typeof this._onTimeout === 'function') {

    const now = TimerWrap.now();

    if (!this._idleStart) this._idleStart = now;

    var delay = this._idleStart + this._idleTimeout - now;

    if (delay < 0) delay = 0;

    // Prevent running cb again when unref() is called during the same cb

    if (this._called && !this._repeat) {

      unenroll(this);

      return;

    }

    const handle = reuse(this);

    if (handle !== null) {

      handle._list = undefined;

    }

    this._handle = handle || new TimerWrap();

    this._handle.owner = this;

    this._handle.start(delay);

    this._handle.unref();

  }

  return this;

};

Timeout.prototype.ref = function() {

  if (this._handle)

    this._handle.ref();

  return this;

};

Timeout.prototype.close = function() {

  this._onTimeout = null;

  if (this._handle) {

    if (destroyHooksExist() &&

        typeof this[async_id_symbol] === 'number' &&

        !this._destroyed) {

      emitDestroy(this[async_id_symbol]);

      this._destroyed = true;

    }

    this._idleTimeout = -1;

    this._handle.close();

  } else {

    unenroll(this);

  }

  return this;

};

注意到 Timeout的原型鏈上有unref 跟ref 兩個(gè)方法,他們分別對(duì)應(yīng)的是對(duì)refedLists 跟unrefedLists的處理,

(劇透一下,timer有精妙的數(shù)據(jù)結(jié)構(gòu)設(shè)計(jì),維持了兩個(gè)鏈表,refedLists 是給Node.js外部的定時(shí)器(即第三方

代碼)使用的,而 unrefedLists 是給內(nèi)部模塊(如 net 、http、http2)使用)

平時(shí)的代碼編寫中我們可能不會(huì)在應(yīng)用層用到它,它主要是給 TimerList生成的時(shí)候用的(埋個(gè)伏筆,后續(xù)講到)

active(timer)

active 是激活,如何激活一個(gè)定時(shí)器呢? 就是把它插入到定時(shí)器隊(duì)列里去,所以active(timer)主要做的是

insert(timer, false), 我們看一下insert的實(shí)現(xiàn)


// The underlying logic for scheduling or re-scheduling a timer.

//

// Appends a timer onto the end of an existing timers list, or creates a new

// TimerWrap backed list if one does not already exist for the specified timeout

// duration.

function insert(item, unrefed, start) {

  const msecs = item._idleTimeout;

  if (msecs < 0 || msecs === undefined) return;

  if (typeof start === 'number') {

    item._idleStart = start;

  } else {

    item._idleStart = TimerWrap.now();

  }

  const lists = unrefed === true ? unrefedLists : refedLists;

  // Use an existing list if there is one, otherwise we need to make a new one.

  var list = lists[msecs];

  if (list === undefined) {

    debug('no %d list was found in insert, creating a new one', msecs);

    lists[msecs] = list = new TimersList(msecs, unrefed);

  }

  if (!item[async_id_symbol] || item._destroyed) {

    item._destroyed = false;

    initAsyncResource(item, 'Timeout');

  }

  L.append(list, item);

  assert(!L.isEmpty(list)); // list is not empty

}

體現(xiàn)node timer編寫者智慧的時(shí)刻到了!

Node.js 會(huì)使用一個(gè)雙向鏈表 來保存所有定時(shí)相同的timer, 對(duì)于同一個(gè)鏈表中的所有timer ,只會(huì)創(chuàng)建一個(gè)Timer對(duì)象。當(dāng)鏈表中前面的timer超時(shí)的時(shí)候,會(huì)出發(fā)回調(diào),在回調(diào)中重新計(jì)算下一次的超時(shí)時(shí)間,然后重置Timer對(duì)象以減少重復(fù)Timer對(duì)象的創(chuàng)建開銷。

看代碼174行,在timer實(shí)例上掛載了一個(gè)item._idleStart = TimerWrap.now(); 屬性, 查閱代碼我們知道,這個(gè)TimerWrap方法 是由底層C++模塊 time_wrap 提供的。


const {

  Timer: TimerWrap,

  setupTimers,

} = process.binding('timer_wrap');

timer_wrap 是橋接層 模塊用來封裝一些底層api 給js調(diào)用


class TimerWrap : public HandleWrap {

public:

  static void Initialize(Local target,

                        Local unused,

                        Local context) {

    Environment* env = Environment::GetCurrent(context);

    Local constructor = env->NewFunctionTemplate(New);

    Local timerString = FIXED_ONE_BYTE_STRING(env->isolate(), "Timer");

    constructor->InstanceTemplate()->SetInternalFieldCount(1);

    constructor->SetClassName(timerString);

    env->SetTemplateMethod(constructor, "now", Now);

    AsyncWrap::AddWrapMethods(env, constructor);

    env->SetProtoMethod(constructor, "close", HandleWrap::Close);

    env->SetProtoMethod(constructor, "ref", HandleWrap::Ref);

    env->SetProtoMethod(constructor, "unref", HandleWrap::Unref);

    env->SetProtoMethod(constructor, "hasRef", HandleWrap::HasRef);

    env->SetProtoMethod(constructor, "start", Start);

    env->SetProtoMethod(constructor, "stop", Stop);

    target->Set(timerString, constructor->GetFunction());

    target->Set(env->context(),

                FIXED_ONE_BYTE_STRING(env->isolate(), "setupTimers"),

                env->NewFunctionTemplate(SetupTimers)

                  ->GetFunction(env->context()).ToLocalChecked()).FromJust();

  }

  size_t self_size() const override { return sizeof(*this); }

? 可以在timer_wrap.cc 中看到TimeWrap是一個(gè)類, 上面定義了一些公有靜態(tài)方法,私有靜態(tài)方法, 在初始化的時(shí)候?qū)⒍x的方法掛載到 contructor原型上,addon導(dǎo)出start,stop, now方法供js層調(diào)用。

? TimerWrap.now()方法獲取當(dāng)前event循環(huán) 時(shí)間,賦值給_idleStart(這很重要大家需要留意)

? 繼續(xù)往下走,會(huì)定義根據(jù)unrefed 的ture/false 決定使用 unrefedLists/refedLists, 在這里,用的是refedLists。 refedLists是一個(gè)空對(duì)象,它將存儲(chǔ)的 key值是超時(shí)時(shí)間, value值是存儲(chǔ) 具有相同超時(shí)timer 的雙向列表。它們的key代表這一組定時(shí)器的超時(shí)時(shí)間, key對(duì)應(yīng)的value, 都是一個(gè)定時(shí)器鏈表,比如 lists[1000]對(duì)應(yīng)的就是由一個(gè)或多個(gè)超時(shí)時(shí)間為1000ms的timer組成的鏈表。

當(dāng)你的代碼中第一次調(diào)用定時(shí)器方法時(shí),例如:

let timer1 = setTimeout(() => {}, 1000);

這時(shí)候,lists對(duì)象中是空的,沒有任何鏈表,Timers就會(huì)在對(duì)應(yīng)的位置上(這個(gè)例子中是lists[1000]) 創(chuàng)建一個(gè)TimersList 作為鏈表頭部,并且把剛才創(chuàng)建的新的timer放入鏈表中。

參見代碼:


  // Use an existing list if there is one, otherwise we need to make a new one.

  var list = lists[msecs];

  if (list === undefined) {

    debug('no %d list was found in insert, creating a new one', msecs);

    lists[msecs] = list = new TimersList(msecs, unrefed);

  }

可以試試把它打印出來:

var timer1 = setTimeout(() => {}, 1000)

timer1._idlePrev //=> TimersList {….}

timer1._idlePrev._idleNext //=> timer1

node 在lib/internal/linklist.js中抽象出鏈表的 基礎(chǔ)操作,每個(gè)item都是一個(gè)個(gè)node層的timer。

我們看看TimersList中做了什么


function TimersList(msecs, unrefed) {

  this._idleNext = this; // Create the list with the linkedlist properties to

  this._idlePrev = this; // prevent any unnecessary hidden class changes.

  this._unrefed = unrefed;

  this.msecs = msecs;

  const timer = this._timer = new TimerWrap();

  timer._list = this;

  if (unrefed === true)

    timer.unref();

  timer.start(msecs);

}

初始化鏈表, 實(shí)例化底層timer, 如上所述,TimerWrap是橋接層,導(dǎo)出了start方法給js 層調(diào)用, 如代碼所示, 實(shí)例化操作完成后,執(zhí)行了timer.start(msecs) 方法


TimerWrap(Environment* env, Local object)

      : HandleWrap(env,

                  object,

                  reinterpret_cast(&handle_),

                  AsyncWrap::PROVIDER_TIMERWRAP) {

    int r = uv_timer_init(env->event_loop(), &handle_);

    CHECK_EQ(r, 0);

  }

  static void Start(const FunctionCallbackInfo& args) {

    TimerWrap* wrap = Unwrap(args.Holder());

    CHECK(HandleWrap::IsAlive(wrap));

    int64_t timeout = args[0]->IntegerValue();

    int err = uv_timer_start(&wrap->handle_, OnTimeout, timeout, 0);

    args.GetReturnValue().Set(err);

  }

首先TimerWrap 會(huì)執(zhí)行uv_timer_init方法,


int uv_timer_init(uv_loop_t* loop, uv_timer_t* handle) {

  uv__handle_init(loop, (uv_handle_t*)handle, UV_TIMER);

  handle->timer_cb = NULL;

  handle->repeat = 0;

  return 0;

}

參數(shù)有兩個(gè): 第一個(gè)是 主event_loop, 第二個(gè)參數(shù)為指向uv_timer_t 的指針, 主要是為了初始化handle

  • handle-> flags 標(biāo)示此timer是否已經(jīng)結(jié)束

  • handle-> type 標(biāo)示此timer的類型,在deps/uv/doc/handle.rst 中有枚舉定義

  • handle-> repeat 是否重復(fù)執(zhí)行

  • handle-> timer_cb 超時(shí)后需要執(zhí)行的回調(diào)

  • handle->timeout 定時(shí)執(zhí)行的時(shí)間

接下來會(huì)執(zhí)行 uv_timer_start方法,


int uv_timer_start(uv_timer_t* handle,

                  uv_timer_cb cb,

                  uint64_t timeout,

                  uint64_t repeat) {

  uint64_t clamped_timeout;

  if (cb == NULL)

    return UV_EINVAL;

  if (uv__is_active(handle))

    uv_timer_stop(handle);

  clamped_timeout = handle->loop->time + timeout;

  if (clamped_timeout < timeout)

    clamped_timeout = (uint64_t) -1;

  handle->timer_cb = cb;

  handle->timeout = clamped_timeout;

  handle->repeat = repeat;

  /* start_id is the second index to be compared in uv__timer_cmp() */

  handle->start_id = handle->loop->timer_counter++;

  heap_insert((struct heap*) &handle->loop->timer_heap,

              (struct heap_node*) &handle->heap_node,

              timer_less_than);

  uv__handle_start(handle);

  return 0;

}

從上述代碼中可以看到,uv_timer_start 接收四個(gè)參數(shù)uv_timer_t handle, 回調(diào)函數(shù),超時(shí)時(shí)間, 是否重復(fù)。根據(jù)傳過來的參數(shù)對(duì)handle進(jìn)行屬性設(shè)置

start_id是由timer_counter自增得到,用來在uv__timer_cmp()中作為第二個(gè)比較指標(biāo),即 先加入的定時(shí)器一定先超時(shí)

將timer節(jié)點(diǎn)插入到最小堆中, 最小堆在 heap-inl.h頭文件中實(shí)現(xiàn)

最小堆結(jié)構(gòu)體定義:


/* A binary min heap.  The usual properties hold: the root is the lowest

* element in the set, the height of the tree is at most log2(nodes) and

* it's always a complete binary tree.

*

* The heap function try hard to detect corrupted tree nodes at the cost

* of a minor reduction in performance.  Compile with -DNDEBUG to disable.

*/

struct heap {

  struct heap_node* min;

  unsigned int nelts;

};

最小堆的根節(jié)點(diǎn)一定是最小的元素,最小堆的高度 最多是 log2(nodes),它經(jīng)常是一個(gè)完全二叉樹, 最小堆的插入節(jié)點(diǎn)的時(shí)間復(fù)雜度是O(lgn),主要是為了優(yōu)化頻繁的timer插入性能

  • heap_node* min 是初始化的最小節(jié)點(diǎn)

  • nelts 表示 最小堆中節(jié)點(diǎn)的個(gè)數(shù)。

最小堆節(jié)點(diǎn)的結(jié)構(gòu)體定義:


struct heap_node {

  struct heap_node* left;

  struct heap_node* right;

  struct heap_node* parent;

};

下面看一下timer節(jié)點(diǎn)是怎樣插入到最小堆中的

image

heap_insert有三個(gè)參數(shù), 最小堆, 新節(jié)點(diǎn), 節(jié)點(diǎn)比較函數(shù)

  • 首先是定義父子節(jié)點(diǎn)指針,初始化新節(jié)點(diǎn)的 left,right,parent節(jié)點(diǎn),

  • 第123-124行根據(jù)nelts 堆節(jié)點(diǎn)的個(gè)數(shù),算出最小堆的高度K,以及最小根節(jié)點(diǎn)到最大葉子節(jié)點(diǎn)的路徑path

  • 139-140行表示找到最后一個(gè)node節(jié)點(diǎn),插入到它后面

  • 146-147 行表示 如果node新節(jié)點(diǎn)比node的parent節(jié)點(diǎn)小,就交換它倆, 直到 新節(jié)點(diǎn)比parent節(jié)點(diǎn)大, 插入操作結(jié)束。

以上是最小堆的插入操作。

繼續(xù)往下走,到了 uv__handle_start(handle) ,


#define uv__handle_start(h)                                                  \

  do {                                                                        \

    assert(((h)->flags & UV__HANDLE_CLOSING) == 0);                          \

    if (((h)->flags & UV__HANDLE_ACTIVE) != 0) break;                        \

    (h)->flags |= UV__HANDLE_ACTIVE;                                          \

    if (((h)->flags & UV__HANDLE_REF) != 0) uv__active_handle_add(h);        \

  }                                                                          \

  while (0)


#define uv__active_handle_add(h)                                              \

  do {                                                                        \

    (h)->loop->active_handles++;                                              \

  }                                                                          \

  while (0)

執(zhí)行一次active handle add操作,并將event_loop的active_handles 加1.

到此為止,我們nodejs中定義的一個(gè)定時(shí)器已經(jīng)加到 定時(shí)器鏈表里了,并且隨著event_loop的執(zhí)行,將會(huì)執(zhí)行超時(shí)定時(shí)器的回調(diào)函數(shù)。

流水賬式的記錄了這么多,可能大家已經(jīng)看暈了,下面借用網(wǎng)上 Starkwang.log 大神的圖來描述一下這個(gè)定時(shí)器鏈表

一開始鏈表是這樣的:

image

插入節(jié)點(diǎn)后:

image

定時(shí)器的超時(shí)時(shí)間不同,不停的插入后,將變成這樣

image

大家注意到每個(gè)鏈表的最左側(cè)是個(gè)TimersList,正如我們前面說的,TimersList包含了我們所要復(fù)用的Timer對(duì)象,也就是底層C++實(shí)現(xiàn)的那個(gè)定時(shí)器,它承擔(dān)了整個(gè)鏈表的計(jì)時(shí)工作。

image

如圖所示,等到一個(gè)超時(shí)時(shí)間 比如1000ms到了之后會(huì)執(zhí)行timer1,

1. 承擔(dān)計(jì)時(shí)任務(wù)的TimerList對(duì)象中的Timer (也就是C++實(shí)現(xiàn)的)觸發(fā)回調(diào),執(zhí)行timer1 所綁定的回調(diào)函數(shù)

2. 把timer1 從鏈表中移除

3. 重新計(jì)算多久后出發(fā)下一次回調(diào)(即timer2 對(duì)應(yīng)的回調(diào)),重置Timer,重復(fù)1過程。

那么剛才我們所描述的定時(shí)器 行為 又是怎么實(shí)現(xiàn) 的呢? 前面我們閱讀了 timer加入鏈表的實(shí)現(xiàn),那么定時(shí)器如何被調(diào)度的呢?帶著這些問題我們繼續(xù)往下閱讀源碼。

image

如上圖代碼所示,在event loop中,執(zhí)行uv__update_time(loop)更新時(shí)間后 立即調(diào)用 uv__run_timers(loop), 可見timer的優(yōu)先級(jí)相當(dāng)高,

uv__run_timers()是在 timer.c中定義,我們繼續(xù)看看 timer.c 做了些什么?

(ps. 大家可能疑惑怎么突然扯到 uv_run() 了,它是從哪出發(fā)執(zhí)行的呢? 它其實(shí)是在node.cc start() 函數(shù)里啟動(dòng)的,即node一開始運(yùn)行就啟動(dòng)它了)


void uv__run_timers(uv_loop_t* loop) {

  struct heap_node* heap_node;

  uv_timer_t* handle;

  for (;;) {

    heap_node = heap_min((struct heap*) &loop->timer_heap);

    if (heap_node == NULL)

      break;

    handle = container_of(heap_node, uv_timer_t, heap_node);

    if (handle->timeout > loop->time)

      break;

    uv_timer_stop(handle);

    uv_timer_again(handle);

    handle->timer_cb(handle);

  }

}

在uv__run_timers, 通過 heap_min取出最小的timer節(jié)點(diǎn), 如果為空則跳出循環(huán),


#define container_of(ptr, type, member) \

  ((type *) ((char *) (ptr) - offsetof(type, member)))

調(diào)用container_of 通過heap_node的偏移取出對(duì)象的首地址, 如果最小timer 的timeout時(shí)間 大于當(dāng)前event loop 的時(shí)間則說明過期時(shí)間還沒到,則退出循環(huán)。 如果到了時(shí)間了, 則先通過uv_timer_stop(handle) 將handle從堆中刪除,如果發(fā)現(xiàn)是重復(fù)的定時(shí)器,就通過uv_timer_again(handle) 再重復(fù)加入到堆中,執(zhí)行handle->timer_cb(handle) 的回調(diào)后繼續(xù)循環(huán)。

還記得uv_timer_start()函數(shù)中的 74-79行么?

image

? handle->timeout = clamped_timeout

? clamped_timeout = handle->loop->time + timeout(定時(shí)器超時(shí)時(shí)間)

所以當(dāng)event loop的時(shí)間更新后只需要去檢查是否 有timer到期 要執(zhí)行即可。

最小堆插入的時(shí)候也是 到期時(shí)間越短的越在前面。

結(jié)語(yǔ):

至此我們基本上已經(jīng)看完了 timer 實(shí)現(xiàn)的整個(gè)流程,整個(gè)timer模塊閃爍著開發(fā)者的智慧精髓

  • 數(shù)據(jù)結(jié)構(gòu)抽象

    • linkedlist.js 抽象出鏈表的基礎(chǔ)操作。
  • 以空間換時(shí)間

    • 相同超時(shí)時(shí)間的定時(shí)器分組,而不是使用一個(gè)unrefTimer,復(fù)雜度降到 O(1)。
  • 對(duì)象復(fù)用

    • 相同超時(shí)時(shí)間的定時(shí)器共享一個(gè)底層的 C的 timer。
  • 80/20法則

    • 優(yōu)化主要路徑的性能。

listOnTimeout

image

timer_wrapper 中在Initialize 的時(shí)候會(huì)setupTimers, 設(shè)置定時(shí)器,每個(gè)event loop 周期都會(huì)去檢查處理定時(shí)器,主要處理操作是在 listOnTimeout中

image

在代碼223行, 會(huì)使用L.peek方法依次取出鏈表中的節(jié)點(diǎn),每取出一個(gè) 定時(shí)器節(jié)點(diǎn),就檢查當(dāng)前event loop的時(shí)間,跟定時(shí)器的啟動(dòng)時(shí)間差值(第224行)

如果差值小于定時(shí)器超時(shí)時(shí)間則繼續(xù)執(zhí)行 uv_timer_start,并返回跳出循環(huán);

如果差值大于等于定時(shí)器超時(shí)時(shí)間,則執(zhí)行L.remove(timer),將定時(shí)器從鏈表中移除,執(zhí)行tryOnTimeout方法


// An optimization so that the try/finally only de-optimizes (since at least v8

// 4.7) what is in this smaller function.

function tryOnTimeout(timer, start) {

  timer._called = true;

  const timerAsyncId = (typeof timer[async_id_symbol] === 'number') ?

    timer[async_id_symbol] : null;

  var threw = true;

  if (timerAsyncId !== null)

    emitBefore(timerAsyncId, timer[trigger_async_id_symbol]);

  try {

    ontimeout(timer, start);

    threw = false;

  } finally {

    if (timerAsyncId !== null) {

      if (!threw)

        emitAfter(timerAsyncId);

      if (!timer._repeat && destroyHooksExist() &&

          !timer._destroyed) {

        emitDestroy(timerAsyncId);

        timer._destroyed = true;

      }

    }

  }

}

function ontimeout(timer, start) {

  const args = timer._timerArgs;

  if (typeof timer._onTimeout !== 'function')

    return promiseResolve(timer._onTimeout, args[0]);

  if (start === undefined && timer._repeat)

    start = TimerWrap.now();

  if (!args)

    timer._onTimeout();

  else

    Reflect.apply(timer._onTimeout, timer, args);

  if (timer._repeat)

    rearm(timer, start);

}

在ontimeout里執(zhí)行 timer._onTimeout()函數(shù), 即定時(shí)器的回調(diào)函數(shù)。

至此定時(shí)器timer的添加到執(zhí)行的代碼都已經(jīng)閱讀完, 整個(gè)timers的流程分析到此結(jié)束。該文是本菜研究學(xué)習(xí)timer源碼的心路歷程,貼出來跟大家分享,希望能幫到后續(xù)學(xué)習(xí)timer模塊的同學(xué),同時(shí)也歡迎大家指正。

參考鏈接:
https://zhuanlan.zhihu.com/p/30763470
https://yjhjstz.gitbooks.io/deep-into-node/content/chapter3/chapter3-1.html
https://github.com/xtx1130/blog/issues/14

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,923評(píng)論 6 535
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,740評(píng)論 3 420
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,856評(píng)論 0 380
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,175評(píng)論 1 315
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,931評(píng)論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,321評(píng)論 1 324
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,383評(píng)論 3 443
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,533評(píng)論 0 289
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,082評(píng)論 1 335
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,891評(píng)論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 43,067評(píng)論 1 371
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,618評(píng)論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,319評(píng)論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,732評(píng)論 0 27
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,987評(píng)論 1 289
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,794評(píng)論 3 394
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 48,076評(píng)論 2 375

推薦閱讀更多精彩內(nèi)容

  • 前天nodejs發(fā)布了新版本4.0,其中涉及到一個(gè)更新比較多的模塊,那就是下面要介紹的timer模塊。 timer...
    淘小杰閱讀 824評(píng)論 1 1
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 134,796評(píng)論 18 139
  • 前言 根據(jù)上一篇文章,我們可知,node對(duì)回調(diào)事件的處理完全是基于事件循環(huán)的tick的,因此具有幾大特征: 1、在...
    游泳的石頭閱讀 6,219評(píng)論 3 23
  • 前言從Node.js進(jìn)入人們的視野時(shí),我們所知道的它就由這些關(guān)鍵字組成 事件驅(qū)動(dòng)、非阻塞I/O、高效、輕量,它在官...
    Www劉閱讀 1,554評(píng)論 0 18
  • 前言 音樂令人著迷,做音樂卻不是一件浪漫的事。徹頭徹尾的音樂人李泳彬,試圖用互聯(lián)網(wǎng)模式為音樂行業(yè)摸索一個(gè)模式。理想...
    一起上閱讀 570評(píng)論 0 1