Параллельные асинхронные Ajax-запросы с использованием jQuery


Я хотел бы обновить страницу на основе результатов нескольких запросов 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 66

13 ответов:

попробуйте это решение, которое может поддерживать любое определенное количество параллельных запросов:

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 $.когда() и $.сделано() - именно то, что вам нужно:

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

обновление: В ответ, данный Яиром Левиэлем, этот ответ устарел. Используйте библиотеку обещаний, например jQuery.когда () или 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);

вот моя попытка непосредственно обратиться к вашему вопросу

в принципе, вы просто создаете и стек вызовов 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>
здесь.php
<?php

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

?>

обновление и еще два года спустя это выглядит безумным, потому что принятый ответ изменился на что-то гораздо лучшее! (Хотя все еще не так хорошо, как ответ Яира Левиэля, используя jQuery when)

18 месяцев спустя, я просто ударил что-то подобное. У меня есть кнопка обновления, и я хочу, чтобы старый контент fadeOut а затем новый контент для fadeIn. Но мне тоже нужно get новый контент. Элемент fadeOut и get асинхронны, но это было бы будет пустой тратой времени запускать их поочередно.

то, что я делаю, действительно то же самое, что и принятый ответ, за исключением формы многоразовой функции. Его основное достоинство заключается в том, что он намного короче, чем другие предложения здесь.

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 массив в том же порядке передаются функции.

запуск нескольких запросов AJAX параллельно

при работе с API иногда требуется выдавать несколько AJAX-запросов на разные конечные точки. Вместо того, чтобы ждать завершения одного запроса перед выпуском следующего, вы можете ускорить работу с jQuery, запрашивая данные параллельно, используя jQuery $.when() функция:

JS

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

функция обратного вызова выполняется, когда оба этих запроса GET завершаются успешно. $.когда () принимает обещания, возвращенные двумя $.get () вызывает и создает новый объект promise. Аргументы R1 и r2 обратного вызова являются массивами, первые элементы которых содержат ответы сервера.

вы могли бы сделать что-то подобное

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

вот реализация с помощью 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/

со следующим расширением JQuery (to может быть записан как автономная функция вы можете сделать это:

$.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, консоль

наиболее профессиональное решение для меня было бы с помощью асинхронность.js и Array.уменьшить вот так:

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

Если результат одного запроса зависит от другого, вы не можете сделать их параллельными.

основываясь на ответе Яира. Вы можете определить обещания 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);
                                });