【问题标题】:How to chain ajax calls using jquery如何使用 jquery 链接 ajax 调用
【发布时间】:2011-12-23 06:24:42
【问题描述】:

我需要在不锁定浏览器的情况下发出一系列N个ajax请求,并且想使用jquery deferred object来完成。

这里是一个简化的例子,有三个请求,但是我的程序可能需要排队超过 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]

如何让延迟对象为我排队?我尝试将 done 更改为 pipe 但得到相同的结果。

这是想要的结果:

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]);

【问题讨论】:

    标签: jquery ajax jquery-deferred deferred


    【解决方案1】:

    使用自定义对象

    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/中输出日志的小提琴

    每个管道调用都会返回一个新的 Promise,该 Promise 又用于下一个管道。注意我只提供了sccess功能,失败应该提供类似的功能。

    在每个解决方案中,通过将 Ajax 调用包装在一个函数中并为列表中的每个项目创建一个新的 Promise 来构建链,从而将 Ajax 调用延迟到需要时。

    我相信自定义对象提供了一种更简单的方式来操作链,但管道可能更适合您的口味。

    注意:从 jQuery 1.8 开始,deferred.pipe() 已被弃用,deferred.then 取代它。

    【讨论】:

    • 这个答案肯定有效,我正在尝试消化所有这些,因为它非常复杂。谢谢!
    • 我想我现在看到了。我的原始代码和您的代码之间的主要区别似乎是您正在为每个请求创建 Deferred 对象,而我试图使用单个 Deferred。我说的对吗?
    • 给您一些具体问题:(1)当您已经从 ajax 调用返回承诺时,为什么要显式返回承诺? (2) 为什么将“this”分配给“self”? (3)当它是原生的jquery队列功能时,你为什么不选择使用pipe()? (4) 当我们为每个请求创建 Deferred 对象时,当我开始将数百个请求送入“队列”时,内存需求是多少? Deferred 对象有多轻量级?
    • 延迟是一个承诺,当它被标记为已解决或失败时调用某些函数
    • 抱歉打扰了。 Tha ajax 调用在我完成链接它们时还没有进行,所以我不能使用他们的承诺,必须创建我自己的承诺,因此是自定义对象。可以使用管道,但我认为多个 when 的意图更清楚。希望对您有所帮助,如果您愿意,请在我使用真正的键盘时提供更多解释
    【解决方案2】:

    注意:从 jquery 1.8 开始,您可以使用 .then 而不是 .pipe.then 函数现在返回一个新的承诺,并且 .pipe 已被弃用,因为它不再需要。请参阅promises spec 了解有关 Promise 的更多信息,并参阅 q.js 了解更简洁的 javascript 承诺库,无需 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。

    【讨论】:

    • 如果您可以在超过三行上评论、解释和格式化此答案,我认为这可能是一个非常好的答案。即使我们有预感reduce 可以使用,对于我们很多人(如果不是所有人)来说,这种事情也很难理解。
    • 很棒的 FP 解决方案,我喜欢它
    【解决方案3】:

    我不完全确定您为什么要这样做,但请保留一份您需要请求的所有 URL 的列表,并且在调用 success 函数之前不要请求下一个。即,success 将有条件地对deferred 进行额外调用。

    【讨论】:

    • 出于客户机密的原因,我无法在此处复制确切的代码,但我确实有充分的理由按顺序链接这些调用。您的解决方案不需要将整个数组传递给 getData 函数吗?
    • 或多或少,或者在作用域链中getData 函数上方的某个位置可用。例如,将两个原始代码块(在您的原始问题中)放在一个闭包中,并与数组组合作为第三部分。您还需要跟踪已经发出了哪些请求 - 但您可以通过在请求元素时弹出元素来处理这个问题(将其视为堆栈)。
    • 这是切题的,但您能否详细说明为什么您看不到我为什么要对 ajax 请求进行排队?我能想到两个非常好的用例:1. 限制同时发送到服务器的请求数量,以及 2. 请求之间的潜在依赖关系。
    • 所有现代网络浏览器都会自动管理每台服务器的连接池,以改善页面加载时间(无论是否延迟)。假设启用 HTTP keep-alives 的往返延迟为 20 毫秒,则 100 个请求将至少需要 2 秒才能按顺序处理 - 与每个主机名可能允许 5 个并发连接相比,这将是 20% 或 0.4 秒- 这是最保守的储蓄估计。 (browserscope.org/?category=network 是一个很好的链接,其中包含有关现代 Web 浏览器中配置的最大并发连接的详细信息。)
    • 感谢您的解释,这确实消除了第一个反对意见。但是,请求依赖是我在这里的主要要求,我保证我需要将它们排队(没有双关语)
    【解决方案4】:

    我在 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();
        }
      });
    };
    

    【讨论】:

    • 是的。我相信 jQuery 的队列系统现在都是基于 Promise 的。不错的替代品。
    【解决方案5】:

    我知道我迟到了,但我相信你的原始代码大部分都很好,但有两个(也许三个)问题。

    由于您对管道参数的编码方式,您的getData(country) 会立即被调用。按照您的方式,getData() 立即执行,结果(ajax 的承诺,但 http 请求立即开始)作为参数传递给pipe()。因此,不是传递回调函数,而是传递一个对象 - 这会导致管道的 new deferred 立即被解析。

    我觉得应该这样

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

    现在它是一个回调函数,当管道的父级 deferred 已解决时将调用它。以这种方式编码会引发第二个问题。在 master deferred 解决之前,不会执行任何 getData()。

    潜在的第三个问题可能是,由于您的所有管道都将连接到 master deferred,因此您实际上并没有链,我想知道它是否可以同时执行它们。文档说回调是按顺序执行的,但是由于您的回调返回一个 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();
    

    【讨论】:

    • 有趣,我得试试这个。我不需要在返回值中捕获序列,但深入了解 jquery 总是好的。
    • 我不确定您在 返回值中的捕获序列 中指的是什么。你能详细说明一下吗?
    • 这是我喜欢的,因为我可以在以后解决它,因为 deferred 仍然是 Deferred 对象,但 promise 只是一个 Promise 对象。例如,稍后我可能会在 Promise 链中添加一些内容,并且只想在完成所有工作后解决它。但是,如果您对在添加回调时执行的回调感到满意,那么接受的答案仍然可以。它们仍然按顺序添加。
    【解决方案6】:

    更新: deferred.pipe 已弃用

    这是已经记录在 jQuery API 中的大量代码。见http://api.jquery.com/deferred.pipe/

    你可以一直用管道输送它们,直到所有 100 个都完成。

    或者,我写了一些东西来进行 N 次调用,并使用已进行的所有调用的数据解析一个函数。注意:它返回的不是超级 XHR 对象的数据。 https://gist.github.com/1219564

    【讨论】:

    • 在我最初发布这个问题时,可用的文档很少。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-04-05
    • 1970-01-01
    • 1970-01-01
    • 2017-08-26
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多