基於dojo.DeferredList的事件等待機制一例

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


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章