it-roy-ru.com

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

});

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

69
Paul

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

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);
  });
});
100
Yehuda Katz

jQuery $ .when () и $ .done () это именно то, что вам нужно:

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

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

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

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

?>
8
Peter Bailey

Выполнить несколько запросов 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
sri_bb

UPDATEИ еще два года спустя это выглядит безумно, потому что принятый ответ изменился на что-то намного лучшее! (Хотя все еще не так хорошо, как ответ Яир Левиэль, используя 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();
  });
});

Поэтому, когда нажимается моя кнопка обновления, я запускаю эффект fadeOut в jQuery, а также мою собственную функцию portlet.content (которая выполняет асинхронную get, создает новый бит контента и передает его), а затем, когда оба завершены, я удаляю старый контент, добавить результат второй функции (которая находится в results[1]) и fadeIn новый контент.

Поскольку fadeOut ничего не передает своей функции завершения, results[0] предположительно содержит undefined, поэтому я игнорирую это. Но если бы у вас было три операции с полезными результатами, они поместили бы каждый слот в массив results в том же порядке, в котором вы передали функции.

7
Daniel Earwicker

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

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

Самым профессиональным решением для меня было бы использование 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
George Mavritsakis

Со следующим расширением 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,console

3
mraxus

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

3
Giovanni Cappellotto

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

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

1
Luca Matteis

Предположим, у вас есть массив имен файлов.

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);
                                });
0
Prashant Saurabh