2011-12-23 235 views
32

我需要在不鎖定瀏覽器的情況下創建一系列N個Ajax請求,並且希望使用jQuery延遲對象來完成此操作。如何使用jquery鏈接ajax調用

這是一個帶有三個請求的簡單示例,但我的程序可能需要排隊超過100個(請注意,這不是確切的用例,實際代碼確實需要確保步驟(N-1)

$(document).ready(function(){ 

    var deferred = $.Deferred(); 

    var countries = ["US", "CA", "MX"]; 

    $.each(countries, function(index, country){ 

     deferred.pipe(getData(country)); 

    }); 

}); 

function getData(country){ 

    var data = { 
     "country": country 
    }; 


    console.log("Making request for [" + country + "]"); 

    return $.ajax({ 
     type: "POST", 
     url: "ajax.jsp", 
     data: data, 
     dataType: "JSON", 
     success: function(){ 
      console.log("Successful request for [" + country + "]"); 
     } 
    }); 

} 

下面是被寫入到控制檯(所有請求並行製成,如預期的響應時間是成正比的每個國家的數據的大小:

執行下一步)之前
Making request for [US] 
Making request for [CA] 
Making request for [MX] 
Successful request for [MX] 
Successful request for [CA] 
Successful request for [US] 

我怎樣才能得到延期對象把這些排隊給我?我試着改變管道,但得到相同的結果。

下面是所期望的結果:

Making request for [US] 
Successful request for [US] 
Making request for [CA] 
Successful request for [CA] 
Making request for [MX] 
Successful request for [MX] 

編輯:

我感謝您的建議使用一個數組來存儲請求參數,但jquery的推遲對象具有排隊請求的能力和我真的很想學習如何充分利用這個功能。

這實際上是我想要做的事:

when(request[0]).pipe(request[1]).pipe(request[2])... pipe(request[N]); 

不過,我想在指定時間的請求進入管道一步,以便有效地利用各遍歷:

deferred.pipe(request[0]); 
deferred.pipe(request[1]); 
deferred.pipe(request[2]); 

回答

29

使用自定義對象

function DeferredAjax(opts) { 
    this.options=opts; 
    this.deferred=$.Deferred(); 
    this.country=opts.country; 
} 
DeferredAjax.prototype.invoke=function() { 
    var self=this, data={country:self.country}; 
    console.log("Making request for [" + self.country + "]"); 

    return $.ajax({ 
     type: "GET", 
     url: "wait.php", 
     data: data, 
     dataType: "JSON", 
     success: function(){ 
      console.log("Successful request for [" + self.country + "]"); 
      self.deferred.resolve(); 
     } 
    }); 
}; 
DeferredAjax.prototype.promise=function() { 
    return this.deferred.promise(); 
}; 


var countries = ["US", "CA", "MX"], startingpoint = $.Deferred(); 
startingpoint.resolve(); 

$.each(countries, function(ix, country) { 
    var da = new DeferredAjax({ 
     country: country 
    }); 
    $.when(startingpoint).then(function() { 
     da.invoke(); 
    }); 
    startingpoint= da; 
}); 

小提琴http://jsfiddle.net/7kuX9/1/

是有點更清晰,最後幾行可以寫成

c1=new DeferredAjax({country:"US"}); 
c2=new DeferredAjax({country:"CA"}); 
c3=new DeferredAjax({country:"MX"}); 

$.when(c1).then(function() {c2.invoke();}); 
$.when(c2).then(function() {c3.invoke();}); 

隨着管

function fireRequest(country) { 
     return $.ajax({ 
      type: "GET", 
      url: "wait.php", 
      data: {country:country}, 
      dataType: "JSON", 
      success: function(){ 
       console.log("Successful request for [" + country + "]"); 
      } 
     }); 
} 

var countries=["US","CA","MX"], startingpoint=$.Deferred(); 
startingpoint.resolve(); 

$.each(countries,function(ix,country) { 
    startingpoint=startingpoint.pipe(function() { 
     console.log("Making request for [" + country + "]"); 
     return fireRequest(country); 
    }); 
}); 

http://jsfiddle.net/k8aUj/1/

編輯:提琴輸出日誌在結果窗口中http://jsfiddle.net/k8aUj/3/

每個管道調用返回一個新的承諾,這反過來用於下水管。請注意,我只提供了sccess函數,應該爲失敗提供類似的函數。

在每個解決方案中,Ajax調用延遲到需要通過在功能包裝他們,併爲每個項目創建列表中的一個新的承諾建鏈。

我相信,自定義對象提供了一個更簡單的方法來操縱鏈,但管道可以更好地滿足您的口味。

注意:自jQuery 1.8起,deferred.pipe()已棄用,deferred.then取而代之。

+0

這個答案絕對有效,我試圖消化所有它,因爲它很複雜。謝謝! – Graham 2011-12-23 17:25:44

+0

我想我現在看到了這個。我原來的代碼和你的代碼之間的主要區別似乎是你爲每個請求創建了Deferred對象,我的試圖使用一個Deferred對象。我對麼? – Graham 2011-12-24 16:30:16

+1

你的一些具體問題:(1)當你已經從ajax調用返回承諾時,你爲什麼明確地返回一個承諾? (2)爲什麼要把「這個」分配給「自我」? (3)爲什麼你不選擇使用管道()時,這是本機jquery隊列功能? (4)當我們爲每個請求創建Deferred對象時,當我開始向「隊列」中提供數百個請求時,內存要求是什麼? Deferred對象有多輕量級? – Graham 2011-12-24 17:08:45

4

我不完全確定你爲什麼要這樣做,但保留你需要請求的所有URL的列表,並且在調用success函數之前不要求下一個URL。 I.E.,success將有條件地撥打deferred

+0

由於客戶端機密性的原因,我無法複製這裏的確切代碼,但我確實有一個非常好的理由來按順序鏈接這些調用。你的解決方案不需要將整個數組傳遞給getData函數嗎? – Graham 2011-12-23 06:34:36

+0

或多或少,或者在作用域鏈中的getData函數上方的某處。例如,將兩個原始的代碼塊(在原始問題中)都放入一個閉包中,並將該數組與第三部分結合使用。您還需要跟蹤哪些請求已經完成 - 但您可以通過在請求時彈出元素(將其視爲堆棧)來處理這些請求。 – ziesemer 2011-12-23 06:38:17

+1

這是切線,但你能否擴展爲什麼你不明白爲什麼我想排隊Ajax請求?有兩個非常好的用例可以想到:1.限制向服務器發送同時發送的請求的數量,以及2.請求之間的潛在依賴關係。 – Graham 2011-12-23 06:53:49

2

更新: deferred.pipe已被棄用

這是是jQuery的API中已經記載的東西很多代碼。看到http://api.jquery.com/deferred.pipe/

你可以只保留管道,直到所有100製成。

或者說,我寫的東西,使n次的調用,並解決已取得的所有呼叫的數據的單一功能。注意:它返回的數據不是超級XHR對象。 https://gist.github.com/1219564

+0

當時我最初發布這個問題的時候很少有文檔可用。 – Graham 2013-04-09 19:45:50

4

我知道我遲到了這一點,但我相信你的原代碼,主要是罰款,但有兩個(也許三)問題。

getData(country)被立即調用,因爲你是如何編碼你管的參數。你的方式,getData()立即執行,結果(ajax的承諾,但http請求立即開始)作爲參數傳遞到pipe()。因此,不要傳遞迴調函數,而是傳遞一個對象 - 這會導致管道的新延遲被立即解決。

我認爲它需要

deferred.pipe(function() { return getData(country); }); 

現在是一個回調函數,當遞延管道的父母已經決定,將被調用。用這種方法編碼會引發第二個問題。在解析主體延遲之前,getData()都不會執行。

潛在的第三個問題可能是,因爲所有的管道都會連接到延遲的主站,所以你並沒有一個鏈,我想知道它是否可以同時執行它們。文檔說,回調是按順序執行的,但由於回調函數返回一個promise並運行異步,所以它們可能仍然有些並行執行。

所以,我認爲你需要像這樣

var countries = ["US", "CA", "MX"]; 
var deferred = $.Deferred(); 
var promise = deferred.promise(); 

$.each(countries, function(index, country) { 
    promise = promise.pipe(function() { return getData(country); }); 
}); 

deferred.resolve(); 
+0

有趣的是,我必須嘗試一下。我沒有要求在返回值中捕獲序列,但是更深入地探索jQuery是很好的。 – Graham 2012-09-20 00:25:13

+0

我不確定你在返回values_中使用_capture序列。你能詳細說明一下嗎? – 2012-09-20 13:48:29

+0

這是我喜歡的,因爲然後我可以在晚些時候解決它,因爲延遲對象是Deferred對象,但promise只是一個Promise對象。例如,我可能會稍後在promise鏈中添加一些內容,並且只在所有工作完成時纔想解決它。但是,如果您在添加回調時執行回調,那麼接受的答案仍然可以。它仍然是爲了添加它們。 – gillyspy 2013-11-21 03:00:44

5

注:在jQuery 1.8,你可以用.then,而不是.pipe.then函數現在返回一個新的承諾,.pipe已被棄用,因爲它不再需要。有關承諾的更多信息,請參閱promises spec;有關承諾的更清晰的庫,請參閱q.js,而不要使用jQuery依賴關係。

countries.reduce(function(l, r){ 
    return l.then(function(){return getData(r)}); 
}, $.Deferred().resolve()); 

如果您喜歡使用q。JS:

//create a closure for each call 
function getCountry(c){return function(){return getData(c)};} 
//fire the closures one by one 
//note: in Q, when(p1,f1) is the static version of p1.then(f1) 
countries.map(getCountry).reduce(Q.when, Q()); 

原來的答覆:

另一個管;不是一件輕鬆的事情,但一點點更緊湊:

countries.reduce(function(l, r){ 
    return l.pipe(function(){return getData(r)}); 
}, $.Deferred().resolve()); 

Reduce documentation可能是開始瞭解上面的代碼是如何工作的最佳場所。基本上,它有兩個參數,一個回調和一個初始值。

該回調迭代應用於數組的所有元素,其中第一個參數是前一次迭代的結果,第二個參數是當前元素。這裏的技巧是getData()返回jquery deferred promise,並且管道確保在當前元素上調用getData之前,前一個元素的getData完成。

第二個參數$.Deferred().resolve()是解析延遲值的習慣用法。它被提供給回調執行的第一次迭代,並確保第一個元素上的getData被立即調用。

+0

如果你可以評論,解釋和格式化這個答案超過三行,我認爲這可能是一個非常好的答案。對於許多人來說,如果不是全部人,即使我們有預感,也可以使用「減少」,但這種事情是非常困難的。 – hippietrail 2013-04-05 07:22:36

+0

偉大的FP解決方案,我喜歡它 – 2013-06-27 10:57:46

2

我已經成功使用jQuery隊列。

$(function(){ 
    $.each(countries, function(i,country){ 
     $('body').queue(function() { 
     getData(country); 
     }); 
    }); 
}); 

var getData = function(country){ 
    $.ajax({ 
    url : 'ajax.jsp', 
    data : { country : country }, 
    type : 'post', 
    success : function() {       
     // Que up next ajax call 
     $('body').dequeue(); 
    }, 
    error : function(){ 
     $('body').clearQueue(); 
    } 
    }); 
}; 
+0

簡單高效,像一個魅力工作謝謝 – Sherlock 2014-11-25 21:49:51