How to chain ajax calls using jquery
我需要在不锁定浏览器的情况下发出一系列N ajax请求,并希望使用jquery延迟对象来完成此任务。
这是一个包含三个请求的简化示例,但我的程序可能需要排队超过100(请注意,这不是确切的用例,实际代码确实需要确保步骤(N-1)成功执行下一步之前步):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | $(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 +"]"); } }); } |
这是写入控制台的内容(所有请求都是并行执行的,响应时间与每个国家/地区的数据大小成正比:
1 2 3 4 5 6 | Making request for [US] Making request for [CA] Making request for [MX] Successful request for [MX] Successful request for [CA] Successful request for [US] |
我怎样才能让延迟对象为我排队?我已经尝试将完成更改为管道,但得到相同的结果。
这是期望的结果:
1 2 3 4 5 6 | 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延迟对象能够对请求进行排队,我真的想学习如何充分利用这个功能。
这实际上是我想要做的:
1 | when(request[0]).pipe(request[1]).pipe(request[2])... pipe(request[N]); |
但是,我想一次一步地将请求分配到管道中,以便有效地使用每个遍历:
1 2 3 | deferred.pipe(request[0]); deferred.pipe(request[1]); deferred.pipe(request[2]); |
使用自定义对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 | 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/
为了更清楚一点,可以编写最后一行
1 2 3 4 5 6 | 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();} ); |
用管子
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | 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/
每个管道调用都返回一个新的promise,该promise又用于下一个管道。请注意,我只提供了sccess函数,应该为失败提供类似的功能。
在每个解决方案中,Ajax调用都会延迟到需要时将它们包装在一个函数中,并为列表中的每个项创建一个新的promise以构建链。
我相信自定义对象提供了一种更简单的操作链的方法,但管道可以更好地满足您的口味。
注意:从jQuery 1.8开始,不推荐使用
注意:从jquery 1.8开始,您可以使用
1 2 3 | countries.reduce(function(l, r){ return l.then(function(){return getData(r)}); }, $.Deferred().resolve()); |
如果你想使用q.js:
1 2 3 4 5 | //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()); |
原始答案:
还有另一根烟斗;不是为了胆小的人,而是为了更紧凑:
1 2 3 | countries.reduce(function(l, r){ return l.pipe(function(){return getData(r)}); }, $.Deferred().resolve()); |
减少文档可能是开始理解上述代码如何工作的最佳位置。基本上,它需要两个参数,一个回调和一个初始值。
回调迭代地应用于数组的所有元素,其中第一个参数是前一次迭代的结果,第二个参数是当前元素。这里的技巧是
第二个参数
我知道我迟到了,但我相信你的原始代码大多数都很好但有两个(也许是三个)问题。
由于您对管道参数的编码方式,您的
我认为它需要
1 | deferred.pipe(function () { return getData(country); }); |
现在它是一个回调函数,将在解析管道的父延迟时调用。以这种方式编码会引发第二个问题。在解析主延迟之前,所有getData()都不会执行。
潜在的第三个问题可能是,因为所有管道都将附加到主延迟,你实际上没有链条,我想知道它是否可以同时执行它们。文档说回调按顺序执行,但由于你的回调返回一个promise并运行异步,所以它们可能仍然会并行执行。
所以,我认为你需要这样的东西
1 2 3 4 5 6 7 8 9 | 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(); |
我不确定你为什么要这样做,但保留一份你需要请求的所有URL列表,并且在调用
我已经成功使用jQuery队列。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | $(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(); } }); }; |
更新:不推荐使用deferred.pipe
对于已经在jQuery API中记录的内容,这是很多代码。见http://api.jquery.com/deferred.pipe/
你可以保持管道直到所有100个制作。
或者,我写了一些东西来进行N次调用,并使用已经进行的所有调用的数据来解析单个函数。注意:它返回的数据不是超级XHR对象。 https://gist.github.com/1219564