Параллельные асинхронные запросы 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);
  });

});

Однако это приводит к тому, что запросы выполняются последовательно. Я бы предпочел способ сделать запросы параллельно и выполнить обновление страницы после завершения. Есть ли способ сделать это?

Ответ 1

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

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

jQuery $. когда() и $. done() именно то, что вам нужно:

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

Ответ 3

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

В принципе, вы просто наращиваете и стек вызовов 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

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

Ответ 5

ОБНОВЛЕНИЕ И еще через два года это выглядит безумным, потому что принятый ответ изменился на что-то намного лучше! (Хотя все еще не так хорошо, как ответ Яира Левиля, используя 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, в том же порядке, в котором вы передавали функции.

Ответ 6

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

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

Выполнять параллельные параллельные запросы 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 успешно завершены. $.when() принимает promises, возвращаемый двумя вызовами $.get(), и создает новый объект обещания. Аргументами r1 и r2 обратного вызова являются массивы, чьи первые элементы содержат ответы сервера.

Ответ 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), когда 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

Ответ 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

Основываясь на ответе Яира. Вы можете динамически определить ajax promises.

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

say suppose you have a array of file name.

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

Hope this helps...!!