web-dev-qa-db-fra.com

Requêtes Ajax asynchrones parallèles à l'aide de jQuery

J'aimerais mettre à jour une page en fonction des résultats de plusieurs demandes ajax/json. Avec jQuery, je peux "chaîner" les callbacks, comme cet exemple très simple et simplifié:

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

});

Cependant, les demandes sont envoyées en série. Je préférerais un moyen de faire les demandes en parallèle et d’effectuer la mise à jour de la page une fois toutes les opérations terminées. Y a-t-il un moyen de faire ça?

69
Paul

Essayez cette solution, qui peut supporter un nombre spécifique de requêtes parallèles:

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 () et $ .done () sont exactement ce dont vous avez besoin:

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

Mise à jour: Selon la réponse donnée par Yair Leviel, cette réponse est obsolète. Utilisez une bibliothèque de promesses, telle que jQuery.when () ou Q.js.


J'ai créé une solution à usage général en tant qu'extension jQuery. Pourriez-vous utiliser des réglages plus précis pour le rendre plus général, mais cela répondait à mes besoins. L’avantage de cette technique par rapport aux autres de cette publication au moment de la rédaction de cet article était que vous pouvez utiliser un type de traitement asynchrone de type [].

Note: J'utiliserais des extensions Rx pour JavaScript au lieu de cela si je pensais que mon client accepterait de prendre une dépendance sur une autre bibliothèque tierce partie :)

// 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

Voici ma tentative de répondre directement à votre question

En gros, il suffit de construire et d’appeler la pile AJAX, de tous les exécuter et une fonction fournie est appelée à la fin de tous les événements - l’argument fourni est un tableau des résultats de toutes les demandes ajax fournies.

Clairement, il s’agit d’un code précoce - vous pourriez obtenir plus de détails en termes de flexibilité.

<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>

voici test.php

<?php

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

?>
8
Peter Bailey

Exécuter plusieurs requêtes AJAX en parallèle

Lorsque vous travaillez avec des API, vous devez parfois envoyer plusieurs demandes AJAX à différents ordinateurs d'extrémité. Au lieu d'attendre qu'une requête soit terminée avant d'émettre la suivante, vous pouvez accélérer les choses avec jQuery en demandant les données en parallèle, en utilisant la fonction $.when() de jQuery:

JS

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

La fonction de rappel est exécutée lorsque ces deux demandes GET se terminent avec succès. $ .when () prend les promesses retournées par deux appels $ .get () et construit un nouvel objet de promesse. Les arguments r1 et r2 du rappel sont des tableaux dont les premiers éléments contiennent les réponses du serveur.

7
sri_bb

UPDATEEt encore deux ans plus tard, cela semble insensé parce que la réponse acceptée a changé pour quelque chose de bien meilleur! (Bien que toujours pas aussi bon que la réponse de Yair Leviel en utilisant when de jQuery)

18 mois plus tard, je viens de frapper quelque chose de similaire. J'ai un bouton d'actualisation, et je veux que l'ancien contenu soit fadeOut, puis que le nouveau contenu soit fadeIn. Mais je dois aussi get le nouveau contenu. La fadeOut et la get sont asynchrones, mais ce serait une perte de temps de les exécuter en série.

Ce que je fais est vraiment la même chose que la réponse acceptée, sauf sous la forme d'une fonction réutilisable. Sa principale vertu est qu’il est beaucoup plus court que les autres suggestions présentées ici.

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

Vous lui passez un tableau de fonctions à exécuter en parallèle. Chaque fonction doit accepter une autre fonction à laquelle elle transmet son résultat (le cas échéant). parallel fournira cette fonction.

Vous lui transmettez également une fonction à appeler lorsque toutes les opérations sont terminées. Cela va recevoir un tableau avec tous les résultats en. Mon exemple était donc:

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

Donc, quand on clique sur mon bouton d'actualisation, je lance l'effet fadeOut de jQuery ainsi que ma propre fonction portlet.content (qui effectue une async get, crée un nouveau contenu et le transmet), puis lorsque tous les deux sont terminés, je supprime l'ancien contenu, ajoute le résultat de la deuxième fonction (qui est dans results[1]) et de fadeIn le nouveau contenu.

Comme fadeOut ne passe rien à sa fonction d'achèvement, results[0] contient vraisemblablement undefined, donc je l'ignore. Mais si vous aviez trois opérations avec des résultats utiles, elles seraient chacune dans le tableau results, dans le même ordre que vous avez passé les fonctions.

7
Daniel Earwicker

tu pourrais faire quelque chose comme ça

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

La solution la plus professionnelle pour moi serait d’utiliser async.js et Array.reduce comme ceci:

        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

Avec l'extension suivante de JQuery (il est possible d'écrire comme une fonction autonome):

$.whenAll({
    val1: $.getJSON('/values/1'),
    val2: $.getJSON('/values/2')
})
    .done(function (results) {
        var sum = results.val1.value + results.val2.value;

        $('#mynode').html(sum);
    });

L'extension 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;
};

Voir l'exemple de jsbin: http://jsbin.com/nuxuciwabu/edit?js,console

3
mraxus

Voici une implémentation utilisant 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);
  });

Le violon associé: http://jsfiddle.net/MdbW2/

3

S'appuyant sur la réponse de Yair. Vous pouvez définir les promesses ajax de manière dynamique. 

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

Si le résultat d'une requête dépend de l'autre, vous ne pouvez pas les rendre parallèles.

1
Luca Matteis

Supposons que vous ayez un tableau de noms de fichiers.

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