【问题标题】:Parallel asynchronous Ajax requests using jQuery使用 jQuery 的并行异步 Ajax 请求
【发布时间】:2010-11-06 19:52:37
【问题描述】:

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

});

但是,这会导致请求按顺序发出。我更喜欢一种并行发出请求的方法,并在所有完成后执行页面更新。有没有办法做到这一点?

【问题讨论】:

    标签: javascript jquery ajax


    【解决方案1】:

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

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

    【讨论】:

    • +1 我在某处听说过 promises 写得不好……显然我需要忘记这一点!
    • 无意冒犯,但这个答案不是远远优于@yehuda-katz 的答案吗? (鉴于这行得通)
    • 这个答案不清楚如何在 ajax 调用完成后访问数据。传递给 myFunc 的内容以及如何访问调用?
    • myFunc 和 myFailure 示例 => codepen.io/jacobgoh101/pen/YaJOzx?editors=0010
    【解决方案2】:

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

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

    【讨论】:

    • 如果我没记错的话,你是'jQuery In Action'的作者?
    • 好书!你的名字让我的脑海里敲响了警钟!
    • 我选择了与您和 Agilefall 类似的东西: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( 结果[urls[0]] / 结果[urls[1]] * 结果[urls[2]]); } }); });
    • 我做过类似的事情。最后,我确实巩固了我的要求。但最好知道如何做到这一点,以防万一。我显示了一个进度条,在这种情况下效果很好,因为代码是回调驱动的。在这种情况下,只需使用 100*((4-done)/4) 作为完成百分比。
    • 看起来“if”中的代码可以执行多次。另外,“done -= 1”是原子的吗?
    【解决方案3】:

    并行运行多个 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 参数是数组,其第一个元素包含服务器响应。

    【讨论】:

      【解决方案4】:

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

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

      【讨论】:

        【解决方案5】:

        更新:根据 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);
        

        【讨论】:

        • 我用过这个,效果很好!...但我确实有一个改进:如果您修改最终的回调调用以使用“应用”,那么您将获得单独的回调参数单个参数列表:即workerCompleteCallback.apply(this,allResults);
        【解决方案6】:

        更新又过了两年,这看起来很疯狂,因为接受的答案已经变成了更好的答案! (虽然仍然不如 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,构建一个新的内容并传递它),然后当两者都是完整的我删除旧内容,附加第二个函数的结果(在results[1]中)和fadeIn新内容。

        由于fadeOut 没有将任何东西传递给它的完成函数,results[0] 大概包含undefined,所以我忽略它。但是,如果您有三个具有有用结果的操作,它们将按照您传递函数的顺序分别插入results 数组。

        【讨论】:

          【解决方案7】:

          你可以这样做

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

          【讨论】:

            【解决方案8】:

            这是一个使用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/

            【讨论】:

              【解决方案9】:

              使用以下 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

              【讨论】:

                【解决方案10】:

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

                【讨论】:

                  【解决方案11】:

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

                  【讨论】:

                  • 一个不依赖另一个,但最终结果取决于每个完成。
                  • 因为它是您返回的数据之间的简单数学运算,所以是的,您可以使用超出范围的变量来跟踪添加的数据。但在大多数情况下,这对于依赖于彼此数据的并行请求来说并不是一个有价值的解决方案。
                  【解决方案12】:

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

                  【讨论】:

                    【解决方案13】:

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

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

                    【讨论】:

                      猜你喜欢
                      • 2016-10-22
                      • 2020-05-03
                      • 1970-01-01
                      • 2011-10-13
                      • 2011-10-04
                      • 1970-01-01
                      • 2015-10-24
                      • 1970-01-01
                      • 2016-02-13
                      相关资源
                      最近更新 更多