用NodeJs实现延迟调用,规避定时任务的闭包问题

很多人在用NodeJs的setTimeout(callback, delay[, arg][, ...])编写定时任务时,习惯上直接操作callback外部的对象object(闭包的特点)。这样做有一个隐患,就是当callback真正执行的时候,外部对象object可能已经被销毁了(比如执行了自定义的销毁方法),导致对object进行的处理结果出现了很大的偏差,程序甚至有可能出现异常而退出。

解决这个问题其实很简单,我们只需要在callback回调中重新通过某种方式获取该对象,检查一下该对象是否已被销毁,即可避免上面描述的问题。但是,当程序中需要很多这样的需求时,并且是一个团队在合作写代码,这样就很难避免上述情况的发生。为了规避定时任务的闭包问题,我写了一个延迟调用类,代码如下:

/**
 * script: delayCall.js
 * description: 延迟调用,规避定时任务的闭包问题
 * authors: [email protected]
 * 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

时间: 2024-12-08 15:06:43

用NodeJs实现延迟调用,规避定时任务的闭包问题的相关文章

【Unity3D】Invoke,InvokeRepeating ,Coroutine 延迟调用,周期性调用

Invoke和InvokeRepeating方法,可以实现延迟调用,和周期调用 第一个是执行一次,第二个是重复执行 void Invoke(string methodName, float time); 第一个参数是方法名(注意是字符串形式),并不是更方便的委托.第二个是延时多少秒.只执行一次. void InvokeRepeating(string methodName, float time, float repeatRate); InvokeRepeating第二个参数是延时多少秒后开始,

延迟调用或多次调用第三方的Web API服务

当我们调用第三方的Web API服务的时候,不一定每次都是成功的.这时候,我们可能会再多尝试几次,也有可能延迟一段时间再去尝试调用服务. Task的静态方法Delay允许我们延迟执行某个Task,此方法可以让我们做到延迟一段时间再去调用服务:多尝试几次调用如何实现呢?可以用循环遍历. 在"使用HttpClient对ASP.NET Web API服务实现增删改查"中,创建了一个ASP.NET Web API项目,本篇沿用此Web API服务. 在ASP.NET Web API项目的同一个

xcode UIImageView创建、图片加载、 音频文件播放、 延迟调用

代码创建 /** 创建UIImageView */ UIImageView * imageView=[[UIImageView alloc]init]; /** 设置尺寸位置 */ imageView.frame=(CGRect){{50,50},{230,230}}; /** 创建图片 */ UIImage * image=[[UIImage alloc]init]; /** 获取图片 */ image=[UIImage imageNamed:@"图片名称"]; /** 把图片给容器

延迟调用-05-GCD

1 // 2 // ViewController.m 3 // 05-GCD延迟调用 4 // 5 // Created by mac on 16/4/21. 6 // Copyright © 2016年 mac. All rights reserved. 7 // 8 9 #import "ViewController.h" 10 11 @interface ViewController () 12 13 @end 14 15 @implementation ViewControll

Python_Tips[2] -&gt; 函数延迟调用与变量值

函数延迟调用与变量值 在一个循环中定义了函数f但是并未对其进行调用,在循环结束后调用,此时i值为3故最终3个函数输出均为9. 1 import dis 2 3 def count(): 4 fs = [] 5 for i in range(1,4): 6 def f(): 7 return i*i 8 fs.append(f) 9 return fs 10 11 def run(): 12 f1, f2, f3 = count() 13 # When the function called, t

Go中defer的延迟调用

// code_006_defer_usage project main.go package main import ( "fmt" ) func test(x int) { fmt.Println(100 / x) } func main() { //关键字 defer ?于延迟一个函数或者方法(或者当前所创建的匿名函数)的执行. //注意,defer语句只能出现在函数或方法的内部. fmt.Println("this is a test") defer fmt

17_defer(延迟调用)关键字的使用

1.defer是延迟调用关键字,只能在函数内部使用 2.总是在main函数结束前调用(和init用法相对) 3.如果有多个defer 遵循先进后出的原则 4.和匿名函数同时使用时,如果匿名函数带有参数,则先把参数传进去,最后调用 例如: package main import "fmt" func Test() { defer fmt.Println("111111") //最后执行 defer fmt.Println("222222") //其

函数的异步、延迟调用

延迟调用: CALL FUNCTION 'ZFM_UPDATE_QALS'  IN UPDATE TASK    "STARTING NEW TASK l_taskname         TABLES           it_prueflos = lt_prueflos. DATA: lw_prueflos TYPE zsqm_prueflos . *  WAIT UP TO 3 SECONDS.   LOOP AT it_prueflos INTO lw_prueflos.     UPD

Delphi Firemonkey在主线程 异步调用函数(延迟调用)

先看下面的FMX.Layouts.pas中一段代码 procedure TCustomScrollBox.MouseDown(Button: TMouseButton; Shift: TShiftState; X, Y: Single); begin FMouseEvents := True; inherited; if (Button = TMouseButton.mbLeft) then begin MousePosToAni(X, Y); AniMouseDown(ssTouch in S