当前位置 : 主页 > 网页制作 > Dojo >

基于dojo.DeferredList的事件等待机制一例

来源:互联网 收集:自由互联 发布时间:2021-06-15
dojo.DeferredList很好地解决了一个事件的触发需要在多个资源上等待的情况。先来回顾一下它的使用: 假设事件doSomething需要两个资源res1和res2同时可用时才能触发,用下面的示例代码来模

dojo.DeferredList很好地解决了一个事件的触发需要在多个资源上等待的情况。先来回顾一下它的使用:

假设事件doSomething需要两个资源res1和res2同时可用时才能触发,用下面的示例代码来模拟:

function waitForResource(/*String*/resourceName){
    var d = new dojo.Deferred();
    setTimeout(function(){
        d.callback(resourceName + " is available");
    }, 1700);
    return d;
}

function doSomething(res){
    console.log(">>> doSomething");
    dojo.forEach(res, function(item, index, array){
        console.log(index, item[0], item[1]);
    });
    console.log("<<< doSomething");
    console.timeEnd("timer");
}

var waitObj1 = waitForResource('res1');
var waitObj2 = waitForResource('res2');

var waitObjects = new dojo.DeferredList([waitObj1, waitObj2]);
console.time("timer");
waitObjects.then(doSomething);

为了验证doSomething函数的确是在资源可用之后才触发,我们使用了firebug的API console.time和console.timeEnd.这两个API需要成对使用,并且都接受一个字符串参数,即Timer的名字。只有当time和timeEnd的调用成对且名字相同时,firebug才会在控制台打印出两个调用之间所花的时间。

下面是运行结果:

>>> doSomething dojo.xd.js (第 14 行)
0 true res1 is available dojo.xd.js (第 14 行)
1 true res2 is available dojo.xd.js (第 14 行)
<<< doSomething dojo.xd.js (第 14 行)
timer: 1713ms dojo.xd.js (第 14 行)

运行时间是1713ms,因此doSomething的确是在资源可用之后才被触发。

非常好用。但是...

上述代码为演示目的,集中在一个模块中,所以变量都可以彼此引用。考虑到这样的情况:我们先用dojo.require请求了一个新的模块文件。dojo.ready()可以确保只有在模块加载成功后再执行后面的代码。这个模块又象服务器请求一段数据,比如是对这个模块的配置。

Module.A:

dojo.require("newModule");
when (newModule is ready and data is ready)
    doSomething;

newModule:

var waitObject = waitForResource(...);

在模块A中,我们可以创建一个Deferred对象来确保dojo将模块加载成功,创建一个DeferredList来确保两个资源(模块,数据)都可用。但问题是如何将waiteObject传给这个DeferredList呢?如果有更多的类似情况呢?

如果应用程序有一个全局的单例对象,比如说叫Application,它有一个state状态来跟踪所有需要等待的资源。这样,我们就可以在程序的任意地方向这个对象注册要等待的资源,而在需要等待的资源才能继续执行的地方来判断能否继续。

var State = function(){
    var op = this.constructor.prototype;
    if (!op.registerEvent){
        op.registerEvent = function(/*String*/resName, /*dojo.Deferred*/df){
            if (!this[resName]){
                this[resName] = {};
                this[resName]._dfList = [];
            }
            this[resName]._dfList.push(df);
            console.log("%d wait object(s) in the queue [%s]",this[resName]._dfList.length, resName);

            this[resName]._dl = new dojo.DeferredList(this[resName]._dfList);
        }
    }

    if (!op.ready){
        op.ready = function ready(/*String*/resName, /*function*/doSomething){
            if (this[resName]._dl){
                var destroy = dojo.hitch(this, this._destroy);
                this[resName]._dl.then(function(res){doSomething(res); destroy(resName)});
            }else{
                doSomething();
            }
        }
    }

    if (!op._destroy){
        op._destroy = function(/*String*/resName){
            console.log("destroy", resName);
            this[resName]._dl = null;
            this[resName]._dfList = [];
        }
    }
}

function waitForResource(/*String*/resourceName, /*Int*/ETA){
    var d = new dojo.Deferred();
    setTimeout(function(){d.callback(resourceName + " is ready");}, ETA);
    return d;
}


var state = new State();
var d1 = waitForResource("ModuleB", 3000);

state.registerEvent("resource batch 1", d1);

function use(){
state.ready("resource batch 1", function(res){
    dojo.forEach(res, function(item, index, array){
        console.log(index, item);
    });

    console.log("use: ready to do something");
});
}

function use2(){
state.ready("resource batch 2", function(res){
    dojo.forEach(res, function(item, index, array){
        console.log(index, item);
    });

    console.log("use2: ready to do something");
});
}

setTimeout(use, 0);

d1 = waitForResource("ModuleB", 2000);
d2 = waitForResource("Data", 1000);
state.registerEvent("resource batch 2", d1);
state.registerEvent("resource batch 2", d2);

setTimeout(use2, 1500);
setTimeout(use2, 1500);
程序不但考虑了一个事件需要等待多个资源的情况,而且考虑了多个事件需要等待多批资源的情况(尽管Javascript引擎在浏览器中的实现是单线程,但它的事件处理机制,加上setTimeout等机制使得上述第二种情况仍然有可能出现)。每一批资源都关联到一个资源名字,或者用待激活的事件名来命名也可以。

现在,只要将State对象设为全局对象,就可以在任何地方,在为某事件请求资源时注册一个等待事件,在使用资源前通过State.ready(/*String*/resourceName, /*Function*/handler)来进行资源请求完成后的处理。如果是请求数据,则需要处理的数据会保存在handler惟一的参数res中。

参数res在dojo.Deferred的handler中是一个对象,它的值是由Deferred对象当初调用callback()函数时传入的。参数res在dojo.DeferredList的handler中是一个数组,每个数组元素具有{/*boolean*/fired, /*object*/data}这样的结构。这也可由上述代码在firebug中运行的结果中看出来:

结合运行结果作一些分析。首先,跟dojo.Deferred/dojo.DeferredList一样,state.ready()并不能阻塞其后代码运行,只能阻塞传入其中的回调函数的运行,直到所请求的资源可用为止。因此,代码段一开始运行就立刻注册了三个资源请求,分别处于两个不同的队列中。

由于第一批资源要在3秒钟以后才可用,第二批资源最迟不超过2秒钟可用,因此函数use2率先被激活。测试代码中调用了两次use2,但第2次调用时没有任何被阻塞的现象,这也是我们期望的。即如果等待的一批资源一旦可用,那么无论其后对同一批资源无论测试多少次,都不应该被阻塞。同样,destroy也被调用两次,不过第二次实际上并没有任何效果(但也没有副作用)。

网友评论