使用jQuery的并行异步Ajax请求

时间:2009-06-29 21:03:05

标签: javascript jquery ajax

我想根据多个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);
  });

});

然而,这导致请求是连续的。我更倾向于一种并行发出请求的方法,并在完成后执行页面更新。有没有办法做到这一点?

13 个答案:

答案 0 :(得分:110)

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

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

答案 1 :(得分:101)

尝试此解决方案,它可以支持任何特定数量的并行查询:

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

答案 2 :(得分:9)

更新:根据Yair Leviel给出的答案,这个答案已经过时了。使用promise库,如jQuery.when()或Q.js。


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

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

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

答案 3 :(得分:8)

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

基本上,你只是构建和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 );

?>

答案 4 :(得分: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函数(它执行异步get,构建一个新的内容并传递它on),然后当两者都完成时,我删除旧内容,追加第二个函数的结果(在results[1]中)和fadeIn新内容。

由于fadeOut未向其完成功能传递任何内容,results[0]可能包含undefined,因此我忽略它。但是如果你有三个有效结果的操作,他们会按照你传递函数的顺序插入results数组。

答案 5 :(得分:7)

并行运行多个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对象。回调的r1和r2参数是数组,其第一个元素包含服务器响应。

答案 6 :(得分: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);
}

答案 7 :(得分: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/

答案 8 :(得分: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,console

答案 9 :(得分: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;
            }));
        });

答案 10 :(得分:1)

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

答案 11 :(得分:1)

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

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

答案 12 :(得分: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);
                                });