Паралельні асинхронні запити Ajax за допомогою jQuery


76

Я хотів би оновити сторінку на основі результатів декількох запитів 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);
  });

});

Однак це призводить до того, що запити подаються послідовно. Мені більше подобається спосіб робити запити паралельно та виконувати оновлення сторінки після завершення. Чи є спосіб зробити це?

Відповіді:


107

Спробуйте це рішення, яке може підтримувати будь-яку певну кількість паралельних запитів:

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

33
Якщо я не помиляюся, ви є автором "jQuery In Action"?
karim79

1
Чудова книга! Твоє ім’я змусило будильника дзвонити в моїй голові!
karim79

2
Я пішов із чимось схожим на ваше та 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; ++ запити; якщо (запити == 3) {$ ('# mynode') .html (результати [urls [0]] / results [urls [1]] * результати [urls [2]]);}});});
Пол

Я зробив щось подібне до цього. Врешті-решт, я все ж консолідував своє прохання. Але добре знати, як це зробити про всяк випадок. Я показав індикатор виконання, який добре працює в цьому випадку, оскільки код керується зворотним викликом. Просто використовуйте 100 * ((4-зроблено) / 4) у цьому випадку для відсотка виконаного.
Носредна

1
Схоже, код всередині "якщо" може бути виконаний кілька разів. Крім того, "done - = 1" атомний?
Gzorg,

119

jQuery $ .when () та $ .done () - саме те, що вам потрібно:

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

+1 Я десь чув, що обіцянки погано складаються ... Мабуть, мені потрібно це забути!
Даніель Ервікер

Не ображайтесь, але хіба ця відповідь далеко не перевершує відповіді @ yehuda-katz? (Враховуючи, що це працює)
Герберт

5
Ця відповідь не зрозуміла, як отримати доступ до даних після завершення викликів ajax. Що передається myFunc і як отримати доступ до дзвінків?
Martin Burch

2
приклад з myFunc та myFailure => codepen.io/jacobgoh101/pen/YaJOzx?editors=0010
Jacob Goh

9

Ось моя спроба безпосередньо звернутися до вашого питання

В основному, ви просто збираєте і стек викликів 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 );

?>

9

Оновлення: Відповідно до відповіді Яіра Левіеля, ця відповідь застаріла. Використовуйте бібліотеку обіцянок, наприклад 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);

2
Я використав це, і це чудово працює! ... але у мене є одне вдосконалення: якщо ви зміните остаточний виклик зворотного виклику, щоб використовувати "застосувати", тоді ви отримаєте окремі аргументи для свого зворотного виклику замість єдиного списку аргументів: тобто workerCompleteCallback.apply (this, allResults);
Нік Перкінс

9

Паралельно запускайте кілька запитів 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

ОНОВИТИ І ще через два роки це виглядає божевільно, бо прийнята відповідь змінилася на щось набагато краще! (Хоча все ще не так добре, як відповідь Яіра Левіеля за допомогою 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масив у тому ж порядку, в якому ви передавали функції.


5

ви могли б зробити щось подібне

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

Ось реалізація з використанням 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

За допомогою наступного розширення 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

Найпрофесійнішим рішенням для мене було б використання 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;
            }));
        });

1

Якщо результат одного запиту залежить від іншого, ви не можете зробити їх паралельними.


1
Одне не залежить від іншого, але кінцевий результат залежить від кожного виконаного.
Пол

1
Оскільки це проста математична операція між даними, які ви отримали назад, то так, ви можете використовувати змінну поза обсягом для відстеження доданих даних. Але в більшості випадків це не є цінним рішенням для паралельних запитів, які залежать від даних інших.
Luca Matteis

1

Спираючись на відповідь Яіра. Ви можете динамічно визначати обіцянки 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);

0

Припустимо, у вас є масив імені файлу.

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);
                                });
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.