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


});

但是,这会导致串行发出请求。我更愿意采用一种并行处理请求的方法,并在所有请求完成后执行页面更新。有什么办法吗?

104032 次浏览

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

试试这个解决方案,它可以支持任意数量的并行查询:

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

你可以做这样的事

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

我想直接回答你的问题

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


?>

更新: 根据 Yair Leviel 给出的答案,这个答案已经过时了。使用一个承诺库,比如 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);

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

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,构建一个新的内容位并传递它) ,然后当两者都完成时,我删除旧的内容,附加第二个函数的结果(在 results[1]中)和 fadeIn新的内容。

由于 fadeOut不向其完成函数传递任何内容,因此 results[0]可能包含 undefined,所以我忽略它。但是,如果您有三个结果有用的操作,它们将按照传递函数的顺序分别插入到 results数组中。

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

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

下面是一个使用 Mbostock/队列的实现:

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/

并行运行多个 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()调用返回的承诺,并构造一个新的承诺对象。回调的 r1r2参数是数组,其第一个元素包含服务器响应。

通过以下 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)扩展名 when All () :

$.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

对我来说,最专业的解决方案是使用 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;
}));
});

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

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

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

我需要多个并行 ajax 调用,而 jquery $.when语法不适合我使用的完整 $.ajax格式。所以我只是创建了一个 setInterval定时器来定期检查每个 ajax 调用何时返回。一旦他们都回来了,我就可以从那里开始了。

我读到可能有浏览器限制,多少同时 Ajax 调用你可以去一次(2?),但是 .$ajax本质上是异步的,因此逐个调用 ajax 将导致并行执行(在浏览器可能的限制范围内)。