nodejs之setTimeout源碼解析
setTimeout是在系統啟動的時候掛載的全局函數。代碼在timer.js。
functionsetupGlobalTimeouts(){consttimers=NativeModule.require('timers');global.clearImmediate=timers.clearImmediate;global.clearInterval=timers.clearInterval;global.clearTimeout=timers.clearTimeout;global.setImmediate=timers.setImmediate;global.setInterval=timers.setInterval;global.setTimeout=timers.setTimeout;}
我們先看一下setTimeout函數的代碼。
functionsetTimeout(callback,after,arg1,arg2,arg3){if(typeofcallback!=='function'){thrownewerrors.TypeError('ERR_INVALID_CALLBACK');}vari,args;switch(arguments.length){// fast casescase1:case2:break;case3:args=[arg1];break;case4: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.0args[i-2]=arguments[i];}break;}//新建一個對象,保存回調,超時時間等數據,是超時哈希隊列的節點consttimeout=newTimeout(callback,after,args,false,false);//啟動超時器active(timeout);//返回一個對象returntimeout;}
其中Timeout函數在lib/internal/timer.js裡定義。
functionTimeout(callback,after,args,isRepeat,isUnrefed){after*=1;// coalesce to number or NaNthis._called=false;this._idleTimeout=after;this._idlePrev=this;this._idleNext=this;this._idleStart=null;this._onTimeout=null;this._onTimeout=callback;this._timerArgs=args;this._repeat=isRepeat?after:null;this._destroyed=false;this[unrefedSymbol]=isUnrefed;this[async_id_symbol]=++async_id_fields[kAsyncIdCounter];this[trigger_async_id_symbol]=getDefaultTriggerAsyncId();if(async_hook_fields[kInit]>0){emitInit(this[async_id_symbol],'Timeout',this[trigger_async_id_symbol],this);}}
由代碼可知,首先創建一個保存相關訊息的對象,然後執行active函數。
constactive=exports.active=function(item){//插入一個超時對像到超時隊列insert(item,false);}functioninsert(item,unrefed,start){//超時時間constmsecs=item._idleTimeout;if(msecs<0||msecs===undefined)return;//如果傳了start則計算是否超時時以start為起點,否則取當前的時間if(typeofstart==='number'){item._idleStart=start;}else{item._idleStart=TimerWrap.now();}//哈希隊列constlists=unrefed===true?unrefedLists:refedLists;varlist=lists[msecs];//沒有則新建一個隊列if(list===undefined){debug('no%dlistwasfoundininsert,creatinganewone',msecs);lists[msecs]=list=newTimersList(msecs,unrefed);}...//把超時節點插入超時隊列L.append(list,item);assert(!L.isEmpty(list));// list is not empty}
從上面的代碼可知,active一個定時器實際上是把新建的timeout對象掛載到一個哈希隊列裡。我們看一下這時候的內存視圖。

當我們創建一個timerList的是時候,就會關聯一個底層的定時器,執行setTimeout時傳進來的時間是一樣的,都會在一條隊列中進行管理,該隊列對應一個定時器,當定時器超時的時候,就會在該隊列中找出超時節點。下面我們看一下new TimeWraper的時候發生了什麼。
TimerWrap(Environment*env,Local<Object>object):HandleWrap(env,object,reinterpret_cast<uv_handle_t*>(&handle_),AsyncWrap::PROVIDER_TIMERWRAP){intr=uv_timer_init(env->event_loop(),&handle_);CHECK_EQ(r,0);}
其實就是初始化了一個libuv的uv_timer_t結構體。然後接著start函數做了什麼操作。
staticvoidStart(constFunctionCallbackInfo<Value>&args){TimerWrap*wrap=Unwrap<TimerWrap>(args.Holder());CHECK(HandleWrap::IsAlive(wrap));int64_ttimeout=args[0]->IntegerValue();interr=uv_timer_start(&wrap->handle_,OnTimeout,timeout,0);args.GetReturnValue().Set(err);}
就是啟動了剛才初始化的定時器。並且設置了超時回調函數是OnTimeout。這時候,就等定時器超時,然後執行OnTimeout函數。所以我們繼續看該函數的代碼。
constuint32_tkOnTimeout=0;staticvoidOnTimeout(uv_timer_t*handle){TimerWrap*wrap=static_cast<TimerWrap*>(handle->data);Environment*env=wrap->env();HandleScopehandle_scope(env->isolate());Context::Scopecontext_scope(env->context());wrap->MakeCallback(kOnTimeout,0,nullptr);}
OnTimeout函數繼續調kOnTimeout,但是該變量在time_wrapper.c中是一個整形,這是怎麼回事呢?這時候需要回lib/timer.js裡找答案。
constkOnTimeout=TimerWrap.kOnTimeout|0;// adds listOnTimeout to the C++ object prototype, as// V8 would not inline it otherwise.//在TimerWrap中是0,給TimerWrap對象掛一個超時回調,每次的超時都會執行該回調TimerWrap.prototype[kOnTimeout]=functionlistOnTimeout(){//拿到該底層定時器關聯的超時隊列,看TimersListvarlist=this._list;varmsecs=list.msecs;//if(list.nextTick){list.nextTick=false;process.nextTick(listOnTimeoutNT,list);return;}debug('timeoutcallback%d',msecs);varnow=TimerWrap.now();debug('now:%d',now);vardiff,timer;//取出隊列的尾節點,即最先插入的節點,最可能超時的,TimeOut對象while(timer=L.peek(list)){diff=now-timer._idleStart;// Check if this loop iteration is too early for the next timer.// This happens if there are more timers scheduled for later in the list.//最早的節點的消逝時間小於設置的時間,說明還沒超時,並且全部節點都沒超時,直接返回if(diff<msecs){//算出最快超時的節點還需要多長時間超時vartimeRemaining=msecs-(TimerWrap.now()-timer._idleStart);if(timeRemaining<0){timeRemaining=1;}//重新設置超時時間this.start(timeRemaining);debug('%dlistwaitbecausediffis%d',msecs,diff);return;}// The actual logic for when a timeout happens.//當前節點已經超時L.remove(timer);assert(timer!==L.peek(list));if(!timer._onTimeout){if(async_hook_fields[kDestroy]>0&&!timer._destroyed&&typeoftimer[async_id_symbol]==='number'){emitDestroy(timer[async_id_symbol]);timer._destroyed=true;}continue;}//執行超時處理tryOnTimeout(timer,list);}
由上可知,TimeWrapper.c裡的kOnTimeout字段已經被改寫成一個函數,所以底層的定時器超時時會執行上面的代碼,即從定時器隊列中找到超時節點執行,直到遇到第一個未超時的節點,然後重新設置超時時間。再次啟動定時器。