80 votes

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

J'aimerais mettre à jour une page en fonction des résultats de plusieurs requêtes ajax/json. En utilisant jQuery, je peux "enchaîner" les callbacks, comme dans cet exemple très simple et dépouillé :

$.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, cela a pour conséquence que les demandes sont faites en série. Je préférerais que les requêtes soient effectuées en parallèle et que la mise à jour de la page soit effectuée une fois qu'elles sont toutes terminées. Existe-t-il un moyen de faire cela ?

126voto

Yair Leviel Points 411

JQuery $.when() et $.done() sont exactement ce dont vous avez besoin :

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

0 votes

+1 J'avais entendu quelque part que les promesses ne composaient pas bien... Apparemment, il faut que je l'oublie !

0 votes

Sans vouloir vous offenser, cette réponse n'est-elle pas de loin supérieure à celle de @yehuda-katz ? (Étant donné que cela fonctionne)

6 votes

Cette réponse n'est pas claire sur la façon d'accéder aux données après la fin des appels ajax. Qu'est-ce qui est transmis à myFunc et comment puis-je accéder aux appels ?

109voto

Yehuda Katz Points 18277

Essayez cette solution, qui peut prendre en charge n'importe quel 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);
  });
});

35 votes

Si je ne me trompe pas, vous êtes l'auteur de "jQuery In Action" ?

1 votes

Grand livre ! Votre nom a fait sonner les alarmes dans ma tête !

2 votes

J'ai utilisé quelque chose de similaire au vôtre et à celui d'agilefall : var results = {} ; var requests = 0 ; var urls = ["values/1", "values/2", "values/3"] ; $.each(urls, function(url) { $. getJSON(url, function(data) { results[url] = data.value ; ++requests ; if (requests == 3) { $('#mynode').html( results[urls[0]] / results[urls[1]] * results[urls[2]]) ; } });; ; $.each urls, function(url) { $. }) ; }) ;

9voto

Peter Bailey Points 62125

Voici ma tentative de répondre directement à votre question.

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

Il s'agit clairement d'un code précoce - vous pourriez aller plus loin 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 le fichier test.php

<?php

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

?>

9voto

pettys Points 1364

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


J'ai créé une solution à usage général comme une extension de jQuery. Elle pourrait être affinée pour la rendre plus générale, mais elle répondait à mes besoins. Au moment de la rédaction de cet article, l'avantage de cette technique par rapport aux autres est que tout un type de traitement asynchrone avec un callback peut être utilisé.

Note : J'utiliserais les extensions Rx pour JavaScript à la place de ceci si je pensais que mon client serait d'accord pour prendre une dépendance sur une autre bibliothèque tierce :)

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

J'ai utilisé cette méthode et elle fonctionne très bien ... mais j'ai une amélioration à apporter : si vous modifiez l'invocation finale du callback pour utiliser "apply", vous obtenez des arguments séparés pour votre callback au lieu d'une liste unique d'arguments : par exemple workersCompleteCallback.apply(this,allResults) ;

7voto

Daniel Earwicker Points 63298

UPDATE Et deux ans plus tard, cela semble insensé car la réponse acceptée a changé pour quelque chose de bien meilleur ! (Bien qu'elle ne soit toujours pas aussi bonne que la réponse de Yair Leviel qui utilise la fonction jQuery when )

18 mois plus tard, je viens de rencontrer quelque chose de similaire. J'ai un bouton de rafraîchissement, et je veux que l'ancien contenu soit fadeOut et ensuite le nouveau contenu vers fadeIn . Mais je dois aussi get le nouveau contenu. Le site fadeOut et le get sont asynchrones, mais ce serait une perte de temps de les exécuter en série.

Ce que je fais est en fait la même chose que la réponse acceptée, mais sous la forme d'une fonction réutilisable. Son principal avantage est qu'elle est beaucoup plus courte 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 assurera cette fonction.

Vous lui passez également une fonction qui sera appelée lorsque toutes les opérations seront terminées. Cette fonction recevra un tableau contenant tous les résultats. Donc mon exemple était :

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

Ainsi, lorsque l'on clique sur mon bouton de rafraîchissement, je lance la fonction jQuery fadeOut l'effet et aussi mon propre portlet.content (qui effectue une opération asynchrone get construit un nouveau contenu et le transmet), puis, lorsque les deux sont terminés, je supprime l'ancien contenu, j'ajoute le résultat de la deuxième fonction (qui se trouve dans le fichier results[1] ) et fadeIn le nouveau contenu.

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

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X