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

来源:互联网 发布:万向信托怎么样 知乎 编辑:程序博客网 时间:2024/05/29 14:12

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中运行的结果中看出来:

image

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

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