Параллельные асинхронные запросы Ajax с использованием jQuery - PullRequest
70 голосов
/ 30 июня 2009

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

112 голосов
/ 08 мая 2012

jQuery $. When () и $. Done () - это именно то, что вам нужно:

$.when($.ajax("/page1.php"), $.ajax("/page2.php"))
  .then(myFunc, myFailure);
100 голосов
/ 30 июня 2009

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

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);
  });
});
9 голосов
/ 21 июня 2010

Обновление: Согласно ответу Яира Левиеля, этот ответ устарел. Используйте библиотеку обещаний, например, 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);
8 голосов
/ 30 июня 2009

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

По сути, вы просто создаете и вызываете стек 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 );

?>
7 голосов
/ 11 марта 2014

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

7 голосов
/ 28 июня 2010

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

5 голосов
/ 30 июня 2009

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

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 голосов
/ 04 февраля 2016

Самым профессиональным решением для меня было бы использование 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 голосов
/ 26 августа 2015

Со следующим расширением 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 голосов
/ 28 августа 2013

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...