使用 Dojo 進行批量 Ajax 請求的多種處理方式

引言

在閱讀本文之前,您需要具備以下知識:

  • Ajax 技術:Ajax 的英文全名是:Asynchronous JavaScript and XML,從名字可以看出 Ajax 是一種非同步的與伺服器業務交互的方式。通常,在瀏覽器內運行的 JavaScript 代碼都是單線程的。Ajax 的非同步交互方式使得前台的 JavaScript 程序不會阻塞在等待伺服器響應的過程中。從而使前台程序能夠一直保持激活和響應性。有關 Ajax 的內容請參考 Ajax 技術資源中心。
  • Dojo 框架:Dojo 是一個強大的前端框架,它提供了方便的 Ajax 方法、豐富的小部件、數據結構、輔助函數、效果和布局幫助。有關 Dojo 的詳細介紹請參考 Dojo 技術專題。
  • XMLHttpRequest 對象:這是一個 JavaScript 對象,用於向伺服器發送請求和接收伺服器的響應。詳細介紹請參考相關資源。
  • 非同步編程模式:這種模式在 web 編程中變得越來越重要,通常用於執行時間可能較長的操作。非同步編程模式不會等待長時間的操作完成或是造成阻塞,而是立即返回一個代表該操作的對象。操作成功或者失敗之後會調用事先對該對象註冊的回調函數。有關非同步編程模式在 web 編程中的應用請參考相關資源。
  • 並發和串列 Ajax 請求:顧名思義,並發 Ajax 請求是指客戶端同時發送多個 Ajax 請求至伺服器端;串列 Ajax 請求是指客戶端在一個 Ajax 請求返回之後再發送下一個 Ajax 請求。
  • Dojo 的應用將貫穿本文,選擇 Dojo 的主要原因有以下兩點:

  • Dojo 提供了專門用於非同步編程模式的對象:dojo.Deferred。並且提供了很多進一步操作 Deferred 對象的機制和方法。通過這些機制和方法能夠方便的管理多個 dojo.Deferred 對象。
  • Dojo 封裝了一些靜態函數(dojo.xhr 函數)使得開發人員能夠很簡單的觸發 Ajax 請求。同時,dojo.xhr 函數返回一個 Deferred 對象用於進行非同步編程操作。
  • 基於上述內容,本文的主要內容可以歸納如下:

  • 描述 dojo.xhr 函數的使用、如何將其應用於非同步編程模式和如何利用非同步編程模式實現小規模的批量 Ajax 請求管理
  • 詳細介紹用 dojo.DeferredList 和 dojox.lang.async.par 實現任意規模的並發 Ajax 請求
  • 詳細介紹用 dojox.lang.async.seq 實現任意規模的串列 Ajax 請求
  • 回頁首

    準備工作伺服器端業務描述

    由於本文主要討論客戶端實現 Ajax 請求。在此不介紹伺服器端的實現方式,並且認為伺服器已經實現了所有我們需要的功能,僅以最簡單的方式描述伺服器端的業務。

    讓我們用一個示例來說明,圖 1 展示了伺服器與客戶端的交互。

    圖 1. 客戶端與伺服器端數據交互示

    現在在伺服器端有一個音樂管理系統,裡面的音樂按照風格進行分類。伺服器提供兩類 REST 服務:

  • /music/{categoryOfMusic} 返回某一個分類的所有歌曲的概要信息
  • /music/{nameOfSong} 返回某一首歌曲的詳細信息
  • 客戶端與伺服器端的數據以 JSON 格式進行交互。

    準備開發環境和調試工具

    下載並安裝 Firefox 瀏覽器,並安裝 Firebug 插件。

    回頁首

    使用 Dojo 進行基本的 Ajax 請求處理dojo.xhr 的使用方法

    HTTP 協議中的四種請求是分別是:Get(讀取),Post(更新),Put(創建),Delete(刪除)。 Dojo 以 XmlHttpRequest 對象為基礎,提供了一組靜態函數來支持這些 HTTP 請求。這些方法定義在 dojo/_base/xhr.js 中,因此在使用的時候不需要顯式的引用。

    清單 1 顯示了一個示例,這個示例獲取所有輕音樂歌曲,並將獲得的數據列印在 Firebug 的 console 面板上。

    清單 1. dojo.xhrGet 的使用

    dojo.xhrGet({ url: "/music/softMusic", sync: false, handleAs: "json", handle: function(response, ioArgs) { console.log(response); } });

    sync 表示當前的 xhr 函數在數據返回前是否阻塞,這個值默認是 false,即不阻塞。有關 Dojo 中 xhr 函數的詳細使用請參考 掌握 Dojo 工具包,第 2 部分: XHR 框架與 Dojo 以獲得更多的信息。

    伺服器以 JSON 格式返回歌曲信息如清單 2 所示:

    清單 2. 伺服器端返回 /music/{categoryOfMusic} 數據示例

    { "songs": [ { name: "softMusic1", singer: "A", category: "soft" }, { name: "softMusic2", singer: "B", category: "soft" }, { name: "softMusic3", singer: "C", category: "soft" } ] }

    dojo.Deferred 對象和為 dojo.xhr 函數添加回調函數

    Dojo 提供的所有 xhr 函數都會返回一個"dojo.Deferred"對象。更多有關"dojo.Deferred"的信息,請參考 dojo.Deferred 參考文檔。Deferred 對象是 Dojo 提供的用於非同步編程模式的強大工具。"dojo.Deferred"有三個狀態,初始化時是"unresolve"狀態;當它所等待的事件發生時 , 進入"resolve" 狀態;當發生錯誤了,進入"reject"狀態。當 Deferred 對象由"unresolve"狀態進入"resolve" 狀態或"reject"狀態時,會調用事先註冊的回調函數。對於 dojo.xhr 返回的 Deferred 對象,所註冊的回調函數會在 Ajax 請求返回時或者出錯時調用。通過註冊回調函數,可以替代的 dojo.xhr 中的 handle 方法。例如上面的一段代碼可以寫成如下所示:

    清單 3. 為 dojo.xhrGet 添加回調函數

    var deferred = dojo.xhrGet({ url: "/music/softMusic", handleAs: "json" }); deferred.addBoth(function(response) { console.log(response); });

    addBoth 是為正常返回和出錯返回添加相同的回調函數。與 dojo.xhr 函數的參數 handle 起相同作用。

    基於回調函數的小規模批量 Ajax 請求處理

    因為 Ajax 請求是一種非同步操作,客戶端不知道什麼時候能獲得返回的數據,所以進行批量 Ajax 請求時,客戶端不能很好的同時管理多個 Ajax 請求返回的數據。例如,當需要多個 Ajax 請求都返回之後將得到的數據一起輸出,不論是分別監測這些 Ajax 請求還是延時一個足夠長的時間以確保所有的 Ajax 請求都返回,都不是明智的方法。

    dojo.xhr 函數可以為 Ajax 請求添加任意的回調函數。這樣在前一次請求的回調函數中再發送 Ajax 請求。當最後一個 Ajax 請求返回時,必然所有的 Ajax 請求都已經返回。通過這種方法,可以實現小規模的批量 Ajax 請求管理。

    例如,回到我們的音樂管理系統。現在需要獲取第 3 首輕音樂的詳細信息,並輸出在 Firebug 的 console 面板上。由於我們事先不知道第 3 首輕音樂的名字,無法直接獲取它的詳細信息。只能先獲得所有的輕音樂,在返回的 JSON 格式數據中找到第三首音樂的概要信息,再發送一次 Ajax 請求獲得該音樂的詳細信息。清單 4 展示了一種實現方式。

    清單 4. 在回調函數中發送另一次 Ajax 請求

    var deferred = dojo.xhrGet({ url: "/music/softMusic", handleAs: "json" }); deferred.then(function(response) { var songs = response.songs; return dojo.xhrGet({ url: "/music/" + songs[2].name, handleAs: "json" }); }, function(errResponse) { console.log(errResponse); }).addBoth(function(response) { console.log(response); });

    Deferred 對象的 then 方法接收兩個參數,這兩個參數都是函數,第一個函數在 Deferred 達到"resovled"時調用,第二個函數在 Deferred 達到"rejected"時調用。

    伺服器以 JSON 格式返回歌曲的詳細信息如清單 5 所示:

    清單 5. 伺服器端返回 /music/{nameOfSong} 數據示例

    { name: "softMusic3", singer: "C", year: "2012-03-21", … category: "soft" }

    這種方法只能串列的處理多個 Ajax 請求,並且,隨著 Ajax 請求數增多,所需要的代碼也需要相應的增加,因此只適合進行小規模批量 Ajax 請求處理。

    回頁首

    使用 Dojo 並發的處理多個 Ajax 請求實現原理

    由於 HTTP 協議是無狀態的(Stateless),HTTP 的無狀態特性簡化了伺服器的設計,使伺服器更容易支持大量並發的 HTTP 請求。在客戶端,採用 Dojo 作為開發工具,由於 dojo.xhr 函數返回的是一個"dojo.Deferred"對象。因此,可以通過管理多個 dojo.xhr 函數返回的 Deferred 對象來管理多個並發的 Ajax 請求。

    用 dojo.DeferredList 和 dojox.lang.async.par 實現比較

    Dojo 中有多種方法管理非同步編程對象"dojo.Deferred"。

    首先,可以用 dojo.DeferredList 管理多個 Deferred 對象。dojo.DeferredList 本身是一個 dojo.Deferred 的子類。因此,它也有相應的三種狀態,也有對應的回調函數。DeferredList 達到"resolved"的條件是所有它所管理的 Deferred 對象都達到"resolved"或"rejected"狀態。在下面的例子中,DeferredList 達到"resolved"的條件是所有的 Ajax 請求都返回(不論是成功返回還是出錯返回),而 DeferredList 的回調函數獲得的參數是一個二元數值對的數組,二元數值對對應了各個 Ajax 請求的返回信息。其中第一個數值是一個 boolean 值,表示 Ajax 請求是否成功返回;第二個數值是 Ajax 請求返回的信息。不論各個 Ajax 請求的返回順序如何,該數組的順序對應於傳入 DeferredList 的 Deferred 對象的順序。

    同時,我們也可以用 dojox.lang.async 模塊中的一個靜態函數 dojox.lang.async.par 管理並發的 Ajax 請求管理。這個靜態函數接受一個函數的數組作為參數,並返回一個"dojo.Deferred"對象。在管理並發的 Ajax 請求時,可以使得傳入的函數返回時調用 dojo.xhr 函數。該函數返回的 Deferred 對象達到"resolved"狀態的條件是上述函數數組中的函數成功返回或者函數返回的 Deferred 對象都達到"resolved"狀態。否則,該 Deferred 對象進入"rejected"狀態。該 Deferred 對象對應"resolved"狀態的回調函數能夠獲得一個數組作為參數,這個數組包含了各個 Ajax 請求的成功返回信息。不論各個 Ajax 請求的返回順序如何,該數組的順序對應於傳入 dojox.lang.async.par 的函數的順序。

    並發處理多個 Ajax 請求的示例

    現在,對於我們的音樂管理系統,我們要同時獲得所有的輕音樂,民謠,鄉村音樂 , 並在 Firebug 的 console 面板顯示。清單 6 展示了用上述的兩種方法實現的示例。

    清單 6. 並發處理 Ajax 請求示例

    dojo.require("dojo.DeferredList"); dojo.require("dojox.lang.async"); function getSoftMusic() { return dojo.xhrGet({ url: "/music/softMusic", handleAs: "json" }); } function getFolkMusic() { return dojo.xhrGet({ url: "/music/folkMusic", handleAs: "json" }); } function getCountryMusic() { return dojo.xhrGet({ url: "/music/countryMusic", handleAs: "json" }); } // 利用 dojo.DeferredList 處理並發的 Ajax 請求 var d1 = getSoftMusic(), d2 = getFolkMusic(), d3 = getCountryMusic(), defList = new dojo.DeferredList([d1, d2, d3]); defList.then(function(results) { dojo.forEach(results, function(result) { console.log(result[1]); }); }); // 利用 dojox.lang.async.par 處理並發的 Ajax 請求 var arrayOfAjax = [getSoftMusic, getFolkMusic, getCountryMusic]; dojox.lang.async.par(arrayOfAjax)().then(function(results) { dojo.forEach(results, function(result) { console.log(result); }); });

    兩種解決方案的對比

    從上面的示例可以看出,上述的兩種方法都是用一個 Deferred 對象管理多個 Deferred 對象(DeferredList 是 Deferred 的子類,因此 DeferredList 的實例也可以認為是一個 Deferred 對象)。通過為作為管理者的 Deferred 對象增加回調函數來通知客戶端程序多個 Ajax 請求已經返回。

    相比之下,用 DeferredList 更為靈活。DeferredList 在默認情況下,會完成所有的 Ajax 請求,即使某一個 Ajax 請求失敗了,最後 DeferredList 仍能夠觸發"resolved"狀態的回調函數,並將相應的信息置於回調函數的參數中。而 dojox.lang.async.par 只會在所有的 Ajax 請求成功返回之後才會將各個 Ajax 請求的返回的信息傳遞給回調函數。當某一個 Ajax 請求失敗了,dojox.lang.async.par 會將排在這個請求之後的所有 Ajax 請求取消。並觸發"rejected"狀態的回調函數。

    回頁首

    使用 Dojo 串列的處理多個 Ajax 請求實現原理

    利用前文中所述的方法可以串列的處理多個 Ajax 請求。但是存在一個問題,當需要串列處理的 Ajax 請求較多時,代碼將顯得又亂又長。並且,很多時候程序事先並不知道要串列處理多少個 Ajax 請求,因為這是由用戶的操作決定的。在 dojox.lang.async 模塊中,有一個靜態函數 dojox.lang.async.seq 能夠很好的幫我們解決這些問題。

    用 dojox.lang.async.seq 的實現和示例

    dojox.lang.async.seq 也是接受一個函數的數組作為參數,並返回一個"dojo.Deferred"對象。在管理串列的 Ajax 請求時,可以使得傳入的函數返回時調用 dojo.xhr 函數。該函數返回的 Deferred 對象達到"resolved"狀態的條件是上述函數數組中的函數成功返回或者函數返回的 Deferred 對象都達到"resolved"狀態。否則,該 Deferred 對象進入"rejected"狀態。

    回到之前的例子,現在我們需要刪除輕音樂,民謠,鄉村音樂。但是很不幸,伺服器規定刪除操作不能並發進行,我們只能在刪除一個分類之後再刪除另一個分類。

    清單 7 展示了實現的示例。

    清單 7. 串列處理 Ajax 請求示例

    dojo.require("dojox.lang.async"); function deleteSoftMusic() { return dojo.xhrDelete({ url: "/music/softMusic", handle: function(response) { console.log(response); } }); } function deleteFolkMusic() { return dojo.xhrDelete({ url: "/music/folkMusic", handle: function(response) { console.log(response); } }); } function deleteCountryMusic() { return dojo.xhrDelete({ url: "/music/countryMusic", handle: function(response) { console.log(response); } }); } var arrayOfAjax = [deleteSoftMusic, deleteFolkMusic, getCountryMusic]; dojox.lang.async.seq(arrayOfAjax)().allBoth(function(result) { console.log(result); });

    當 dojox.lang.async.seq 管理的某一個 Ajax 請求失敗了,排在其之後的 Ajax 請求將不再執行。並且 dojox.lang.async.seq 返回的 Deferred 對象會進入"rejected"狀態。

    回頁首

    總結

    通過以上的介紹和舉例,我們了解了如何以 Dojo 作為工具,管理批量 Ajax 請求。Ajax 請求是一種非同步操作,客戶端無法知道這種非同步操作什麼時候返回,是否能成功返回。因此,批量處理 Ajax 請求的難點在於同時管理。本文講述了一種結合 dojo.xhr 函數和 dojo.Deferred 對象的管理方式。用這種方式能夠以多種方式管理批量 Ajax 請求,希望能為您的實際應用帶來一定的啟發。


    推薦閱讀:

    北航教授性騷擾女學生,這次他們的處理方式很有擔當!
    孩子大了,不聽話?學學這位媽媽的處理方式!

    TAG:方式 | 處理方式 | Ajax | 處理 | 批量 |