不少人在用NodeJs的setTimeout(callback, delay[, arg][, ...])編寫定時任務時,習慣上直接操做callback外部的對象object(閉包的特色)。這樣作有一個隱患,就是當callback真正執行的時候,外部對象object可能已經被銷燬了(好比執行了自定義的銷燬方法),致使對object進行的處理結果出現了很大的誤差,程序甚至有可能出現異常而退出。javascript
解決這個問題其實很簡單,咱們只須要在callback回調中從新經過某種方式獲取該對象,檢查一下該對象是否已被銷燬,便可避免上面描述的問題。可是,當程序中須要不少這樣的需求時,而且是一個團隊在合做寫代碼,這樣就很難避免上述狀況的發生。爲了規避定時任務的閉包問題,我寫了一個延遲調用類,代碼以下:html
/** * script: delayCall.js * description: 延遲調用,規避定時任務的閉包問題 * authors: alwu007@sina.cn * date: 2016-04-19 */ var util = require('util'); var PQueue = require('./pqueue').PQueue; /** * 延遲調用類 * @param search_func 對象查找函數 */ var DelayCall = exports.DelayCall = function(search_func) { //延遲調用序號 this._call_seq = 0; //延遲調用映射 this._call_map = {}; //延遲調用隊列 this._call_queue = new PQueue(DelayCall.compare); //對象查找方法 this._search_func = search_func; //設置間隔定時器。FIXME:能夠改成在框架的心跳機制中去執行run方法 //注:setTimeout不支持修改系統時間 this._interval_id = setInterval(() => { this.run(); }, 1000); }; //比較延遲調用 DelayCall.compare = function(call1, call2) { var time_diff = call1.exec_time - call2.exec_time; if (time_diff) { return time_diff; } else { return call1._call_id - call2._call_id; } }; //延遲調用序號自增 DelayCall.prototype._addSequence = function() { return ++ this._call_seq; }; /** * 延遲調用 * @param id 對象查找方法_search_func根據id查找出調用者對象 * @param method_name 調用者對象上要延遲調用的方法名 * @param params 要延遲調用的方法參數 * @param delay 延遲時間,單位秒 */ DelayCall.prototype.call = function(id, method_name, params, delay) { var call_id = this._addSequence(); var exec_time = Date.now() + delay * 1000; var call_elem = { _call_id: call_id, id: id, method_name: method_name, params: params, delay: delay, exec_time: exec_time, _canceled: false, }; this._call_queue.enQueue(call_elem); this._call_map[call_id] = call_elem; return call_id; }; //取消延遲調用 DelayCall.prototype.cancelCall = function(call_id) { var call_elem = this._call_map[call_id]; if (call_elem) { delete this._call_map[call_id]; call_elem._canceled = true; } }; //運轉一次 DelayCall.prototype.run = function() { var now = Date.now(); var pqueue = this._call_queue; var search_func = this._search_func; var call_elem = pqueue.getHead(); while (call_elem) { if (call_elem._canceled) { pqueue.deQueue(); } else { if (now < call_elem.exec_time) { break; } else { //從隊列和映射中刪除 pqueue.deQueue(); delete this._call_map[call_elem._call_id]; //執行對象的方法 var obj = search_func(call_elem.id); if (obj && typeof obj[call_elem.method_name] == 'function') { obj[call_elem.method_name](call_elem.params); } } } call_elem = pqueue.getHead(); } };
PQueue的實現請參考個人餓另外一篇博文:用NodeJs實現優先級隊列PQueuejava