2011-12-23 95 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