用NodeJs實現延遲調用,規避定時任務的閉包問題

不少人在用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

相關文章
相關標籤/搜索