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也被調用兩次,不過第二次實際上並沒有任何效果(但也沒有副作用)。