很多人在用NodeJs的setTimeout(callback, delay[, arg][, ...])编写定时任务时,习惯上直接操作callback外部的对象object(闭包的特点)。这样做有一个隐患,就是当callback真正执行的时候,外部对象object可能已经被销毁了(比如执行了自定义的销毁方法),导致对object进行的处理结果出现了很大的偏差,程序甚至有可能出现异常而退出。
解决这个问题其实很简单,我们只需要在callback回调中重新通过某种方式获取该对象,检查一下该对象是否已被销毁,即可避免上面描述的问题。但是,当程序中需要很多这样的需求时,并且是一个团队在合作写代码,这样就很难避免上述情况的发生。为了规避定时任务的闭包问题,我写了一个延迟调用类,代码如下:
/** * 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实现优先级队列PQueue