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也被调用两次,不过第二次实际上并没有任何效果(但也没有副作用)。