使用jQuery进行并行异步Ajax请求

78

我想基于多个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);
  });

});

然而这样会导致请求串行执行。我更希望能找到一种并行地发起请求的方式,并在所有请求完成后更新页面。是否有这样的方法可行?

14个回答

124

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

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

+1 我曾经听说过 Promise 不太容易组合使用... 显然我需要忘记这个观点! - Daniel Earwicker
不冒犯,但是相比@yehuda-katz的答案,这个答案难道不更优秀吗?(假设这个能够工作) - Herbert
6
这个答案并没有明确说明如何在ajax调用完成后访问数据。myFunc函数中传递了什么参数,我该如何访问这些调用? - Martin Burch
2
例如myFunc和myFailure => https://codepen.io/jacobgoh101/pen/YaJOzx?editors=0010 - Jacob Goh

108

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

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

35
如果我没记错的话,您是《jQuery In Action》的作者吧? - karim79
1
好书!你的名字让我脑海中响起了警报! - karim79
2
我选择了类似于你和agilefall的方法:var results = {}; var requests = 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]]); } }); }); - Paul
1
看起来在“if”语句内的代码可能会被执行多次。此外,“done -= 1”是原子操作吗? - Gzorg
这个是否更好地阅读为:```var someArray = [1,2,3,4,5]; var done = someArray.length; // 总请求数量 var sum = 0;/* 普通循环不会创建新的作用域 */ $(someArray).each(function() { var number = this; $.getJSON("/values/" + number, function(data) { sum += data.value; done -= 1; if(done == 0) $("#mynode").html(sum); }); });```? - Jinxed
显示剩余6条评论

12

同时运行多个 AJAX 请求

在使用 API 时,有时需要向不同的端点发出多个 AJAX 请求。您可以使用 jQuery 的 $.when() 函数并行请求数据,而不是在一个请求完成后再发出下一个请求,从而加快速度:

JS

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

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


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

?>

尽管这是一个很好的解决方案,但 OP 要求 jQuery,而不是 Prototype。 - Tobse

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

2
我已经使用过这个,它非常好用!但是我有一个改进建议:如果你修改最终的回调调用来使用 "apply",那么你会得到单独的参数而不是一个参数列表传递给你的回调函数:即 workersCompleteCallback.apply(this,allResults)。 - Nick Perkins

7

更新 再过两年,这看起来就像是疯了,因为被接受的答案已经改变成更好的东西了!(虽然还不如Yair Leviel使用jQuery的when答案好)

18个月后,我遇到了类似的情况。我有一个刷新按钮,我希望旧内容淡出,然后新内容淡入。但我也需要获取新内容。 淡出获取都是异步的,但按顺序运行它们会浪费时间。

我做的与被接受的答案基本相同,只是以可重用函数的形式呈现。它的主要优点是比其他建议要短得多。

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数组中。

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

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

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

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/


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接