使用jQuery的并行异步Ajax请求


76

我想根据多个ajax / json请求的结果来更新页面。使用jQuery,我可以“链接”回调,例如以下非常简单的示例:

$.getJSON("/values/1", function(data) {
  // data = {value: 1}
  var value_1 = data.value;

  $.getJSON("/values/2", function(data) {
    // data = {value: 42}
    var value_2 = data.value;

    var sum = value_1 + value_2;

    $('#mynode').html(sum);
  });

});

但是,这导致请求是串行发出的。我宁愿以一种并行方式发出请求,并在完成所有操作后执行页面更新的方法。有什么办法吗?

Answers:


107

尝试以下解决方案,该解决方案可以支持任意数量的并行查询:

var done = 4; // number of total requests
var sum = 0;

/* Normal loops don't create a new scope */
$([1,2,3,4,5]).each(function() {
  var number = this;
  $.getJSON("/values/" + number, function(data) {
    sum += data.value;
    done -= 1;
    if(done == 0) $("#mynode").html(sum);
  });
});

33
如果我没记错的话,您是《 jQuery In Action》的作者吗?
karim79

1
好书!你的名字使警报响彻我的脑海!
karim79

2
我使用了类似于您的方法和敏捷方法的方法:var results = {}; var个请求= 0; var urls = [“ values / 1”,“ values / 2”,“ values / 3”]; $ .each(urls,function(url){$ .getJSON(url,function(data){results [url] = data.value; ++ requests; if(requests == 3){$('#mynode') .html(results [urls [0]] / results [urls [1]] * results [urls [2]]);}});});
保罗

我已经做了类似的事情。最后,我确实合并了我的请求。但是最好知道如何做以防万一。我显示了一个进度条,在这种情况下效果很好,因为代码是由回调驱动的。在这种情况下,只需使用100 *((4-done)/ 4)即可完成百分比。
Nosredna,2009年

1
看起来“ if”中的代码可以执行多次。另外,“完成-= 1”是原子的吗?
Gzorg

119

jQuery $ .when()$ .done()正是您需要的:

$.when($.ajax("/page1.php"), $.ajax("/page2.php"))
  .then(myFunc, myFailure);

+1我曾在某个地方听到过诺言不佳的消息……显然我需要忘记这一点!
Daniel Earwicker

没有冒犯,但是这个答案远不是@ yehuda-katz的答案吗?(鉴于这样做有效)
赫伯特(Herbert

5
关于ajax调用完成后如何访问数据的答案尚不清楚。什么被传递给myFunc?如何访问电话?
马丁·伯奇


9

这是我尝试直接解决您的问题的尝试

基本上,您只需构建并执行AJAX调用堆栈,然后全部执行它们,然后在所有事件完成时调用提供的函数-提供的参数是所有提供的ajax请求的结果数组。

显然,这是早期代码-在灵活性方面,您可以对此进行详细说明。

<script type="text/javascript" src="http://jqueryjs.googlecode.com/files/jquery-1.3.2.min.js"></script>
<script type="text/javascript">

var ParallelAjaxExecuter = function( onComplete )
{
  this.requests = [];
  this.results = [];
  this.onComplete = onComplete; 
}

ParallelAjaxExecuter.prototype.addRequest = function( method, url, data, format )
{
  this.requests.push( {
      "method"    : method
    , "url"       : url
    , "data"      : data
    , "format"    : format
    , "completed" : false
  } )
}

ParallelAjaxExecuter.prototype.dispatchAll = function()
{
  var self = this;
  $.each( self.requests, function( i, request )
    {
    request.method( request.url, request.data, function( r )
    {
      return function( data )
      {
        console.log
        r.completed = true;
        self.results.push( data );
        self.checkAndComplete();
      }
    }( request ) )
  } )
}

ParallelAjaxExecuter.prototype.allRequestsCompleted = function()
{
  var i = 0;
  while ( request = this.requests[i++] )
  {
    if ( request.completed === false )
    {
      return false;
    }
  }
  return true;
},

ParallelAjaxExecuter.prototype.checkAndComplete = function()
{
  if ( this.allRequestsCompleted() )
  {
    this.onComplete( this.results );
  }
}

var pe = new ParallelAjaxExecuter( function( results )
{
  alert( eval( results.join( '+' ) ) );
} );

pe.addRequest( $.get, 'test.php', {n:1}, 'text' );
pe.addRequest( $.get, 'test.php', {n:2}, 'text' );
pe.addRequest( $.get, 'test.php', {n:3}, 'text' );
pe.addRequest( $.get, 'test.php', {n:4}, 'text' );

pe.dispatchAll();

</script>

这是test.php

<?php

echo pow( $_GET['n'], 2 );

?>

18
OO太过分了。
jmah 2011年

9

更新:根据Yair Leviel给出的答案,此答案已过时。使用Promise库,例如jQuery.when()或Q.js。


我创建了一个通用解决方案作为jQuery扩展。可以使用一些微调使其更通用,但它满足我的需求。截至撰写本文时,此技术相对于此发布中的其他技术的优势在于,可以使用带有回调的任何类型的异步处理。

注意:如果我认为我的客户可以依赖于另一个第三方库,那么我会使用Rx扩展而不是JavaScript扩展:)

// jQuery extension for running multiple async methods in parallel
// and getting a callback with all results when all of them have completed.
//
// Each worker is a function that takes a callback as its only argument, and
// fires up an async process that calls this callback with its result.
//
// Example:
//      $.parallel(
//          function (callback) { $.get("form.htm", {}, callback, "html"); },
//          function (callback) { $.post("data.aspx", {}, callback, "json"); },
//          function (formHtml, dataJson) { 
//              // Handle success; each argument to this function is 
//              // the result of correlating ajax call above.
//          }
//      );

(function ($) {

    $.parallel = function (anyNumberOfWorkers, allDoneCallback) {

    var workers = [];
    var workersCompleteCallback = null;

    // To support any number of workers, use "arguments" variable to
    // access function arguments rather than the names above.
    var lastArgIndex = arguments.length - 1;
    $.each(arguments, function (index) {
        if (index == lastArgIndex) {
            workersCompleteCallback = this;
        } else {
            workers.push({ fn: this, done: false, result: null });
        }
    });

    // Short circuit this edge case
    if (workers.length == 0) {
        workersCompleteCallback();
        return;
    }

    // Fire off each worker process, asking it to report back to onWorkerDone.
    $.each(workers, function (workerIndex) {
        var worker = this;
        var callback = function () { onWorkerDone(worker, arguments); };
        worker.fn(callback);
    });

    // Store results and update status as each item completes.
    // The [0] on workerResultS below assumes the client only needs the first parameter
    // passed into the return callback. This simplifies the handling in allDoneCallback,
    // but may need to be removed if you need access to all parameters of the result.
    // For example, $.post calls back with success(data, textStatus, XMLHttpRequest).  If
    // you need textStatus or XMLHttpRequest then pull off the [0] below.
    function onWorkerDone(worker, workerResult) {
        worker.done = true;
        worker.result = workerResult[0]; // this is the [0] ref'd above.
        var allResults = [];
        for (var i = 0; i < workers.length; i++) {
            if (!workers[i].done) return;
            else allResults.push(workers[i].result);
        }
        workersCompleteCallback.apply(this, allResults);
    }
};

})(jQuery);

2
我已经使用了它,并且效果很好!...但是我确实有一个改进:如果您将最终的回调调用修改为使用“ apply”,那么您将为回调获得单独的参数,而不是一个参数列表: workerCompleteCallback.apply(this,allResults);
Nick Perkins 2012年

9

并行运行多个AJAX请求

使用API​​时,有时需要向不同的端点发出多个AJAX请求。您可以使用jQuery的$.when()功能通过并行请求数据来加快jQuery的运行速度,而不必等待发出一个请求之后再等待一个请求完成:

JS

$.when($.get('1.json'), $.get('2.json')).then(function(r1, r2){
   console.log(r1[0].message + " " + r2[0].message);
});

当这两个GET请求都成功完成时,将执行回调函数。$.when()接受两个$.get()调用返回的promise ,并构造一个新的promise对象。回调的r1r2参数是数组,其第一个元素包含服务器响应。


7

更新又过了两年,这看起来很疯狂,因为公认的答案已经变得更好了!(尽管仍然不如Yair Leviel使用jQuery的答案when

18个月后,我碰到了类似的东西。我有一个刷新按钮,我想要旧内容fadeOut,然后想要新内容fadeIn。但是我还需要get新的内容。该fadeOutget是异步的,但是这将是一次连续运行它们的浪费。

除了可重用函数的形式外,我所做的与接受的答案实际上相同。它的主要优点是,它比此处的其他建议短得多。

var parallel = function(actions, finished) {

  finishedCount = 0;
  var results = [];

  $.each(actions, function(i, action) {

    action(function(result) {

      results[i] = result;
      finishedCount++;

      if (finishedCount == actions.length) {
        finished(results);
      }
    });
  });
};

您将传递给它的函数数组并行运行。每个函数都应接受将结果传递给它的另一个函数(如果有)。parallel将提供该功能。

当所有操作完成时,还向它传递一个要调用的函数。这将收到一个包含所有结果的数组。所以我的示例是:

refreshButton.click(function() {

  parallel([
       function(f) { 
         contentDiv.fadeOut(f); 
       },
       function(f) { 
         portlet.content(f); 
       },
     ], 
     function(results) {
      contentDiv.children().remove();
      contentDiv.append(results[1]);
      contentDiv.fadeIn();
  });
});

因此,当单击我的刷新按钮时,我将启动jQuery的fadeOut效果以及我自己的portlet.content函数(该函数执行async get,构建新的内容并将其继续传递),然后当两者都完成时,我删除旧内容,并附加结果第二个功能(位于中results[1]fadeIn的内容和新内容。

由于fadeOut未将任何内容传递给其完成功能,因此results[0]可能包含undefined,因此我将其忽略。但是,如果您进行了三个操作并获得了有用的结果,它们将results按照传递函数的相同顺序分别插入阵列。


5

你可以做这样的事情

var allData = []
$.getJSON("/values/1", function(data) {
    allData.push(data);
    if(data.length == 2){
      processData(allData) // where process data processes all the data
    }
});

$.getJSON("/values/2", function(data) {
    allData.push(data);
    if(data.length == 2){
        processData(allData) // where process data processes all the data
    }
});

var processData = function(data){
     var sum = data[0] + data[1]
     $('#mynode').html(sum);
}

3

这是使用mbostock / queue的实现:

queue()
  .defer(function(callback) {
    $.post('/echo/json/', {json: JSON.stringify({value: 1}), delay: 1}, function(data) {
      callback(null, data.value);
    });
  })
  .defer(function(callback) {
    $.post('/echo/json/', {json: JSON.stringify({value: 3}), delay: 2}, function(data) {
      callback(null, data.value);
    });
  })
  .awaitAll(function(err, results) {
    var result = results.reduce(function(acc, value) {
      return acc + value;
    }, 0);
    console.log(result);
  });

相关的提琴:http : //jsfiddle.net/MdbW2/


3

使用以下JQuery扩展(可以作为独立函数编写,您可以执行以下操作:

$.whenAll({
    val1: $.getJSON('/values/1'),
    val2: $.getJSON('/values/2')
})
    .done(function (results) {
        var sum = results.val1.value + results.val2.value;

        $('#mynode').html(sum);
    });

jQuery(1.x)扩展whenAll():

$.whenAll = function (deferreds) {
    function isPromise(fn) {
        return fn && typeof fn.then === 'function' &&
            String($.Deferred().then) === String(fn.then);
    }
    var d = $.Deferred(),
        keys = Object.keys(deferreds),
        args = keys.map(function (k) {
            return $.Deferred(function (d) {
                var fn = deferreds[k];

                (isPromise(fn) ? fn : $.Deferred(fn))
                    .done(d.resolve)
                    .fail(function (err) { d.reject(err, k); })
                ;
            });
        });

    $.when.apply(this, args)
        .done(function () {
            var resObj = {},
                resArgs = Array.prototype.slice.call(arguments);
            resArgs.forEach(function (v, i) { resObj[keys[i]] = v; });
            d.resolve(resObj);
        })
        .fail(d.reject);

    return d;
};

请参阅jsbin示例:http://jsbin.com/nuxuciwabu/edit?js,控制台


3

对我来说,最专业的解决方案是使用async.js和Array.reduce,如下所示:

        async.map([1, 2, 3, 4, 5], function (number, callback) {
            $.getJSON("/values/" + number, function (data) {
                callback(null, data.value);
            });
        }, function (err, results) {
            $("#mynode").html(results.reduce(function(previousValue, currentValue) {
                return previousValue + currentValue;
            }));
        });

1

如果一个请求的结果取决于另一个请求,则不能使它们并行。


1
一个不依赖另一个,但是最终结果取决于每个完成的过程。
保罗

1
由于这是您返回的数据之间的简单数学运算,因此可以,您可以使用超出范围的变量来跟踪添加的数据。但是在大多数情况下,这对于依赖于彼此数据的并行请求来说并不是一个有价值的解决方案。
卡·马太斯

1

以Yair的答案为基础。您可以动态定义ajax承诺。

var start = 1; // starting value
var len = 2; // no. of requests

var promises = (new Array(len)).fill().map(function() {
    return $.ajax("/values/" + i++);
});

$.when.apply($, promises)
  .then(myFunc, myFailure);

0

假设您有一个文件名数组。

var templateNameArray=["test.html","test2.html","test3.html"];

htmlTemplatesLoadStateMap={};
var deffereds=[];
  for (var i = 0; i < templateNameArray.length; i++)
       {
        if (!htmlTemplatesLoadStateMap[templateNameArray[i]]) 
            {         
              deferreds.push($.get("./Content/templates/" +templateNameArray[i], 

                  function (response, status, xhr) {
                      if (status == "error") { } 
                        else {
                                $("body").append(response);
                               }
                         }));             
htmlTemplatesLoadStateMap[templateNameArray[i]] = true;
                       }
                  }
                                      $.when.all(deferreds).always(function(resultsArray) {   yourfunctionTobeExecuted(yourPayload);
                                });
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.