105 votes

Comment puis-je attendre un ensemble de fonctions de rappel asynchrones ?

J'ai du code qui ressemble à ceci en javascript:

forloop {
    //appel asynchrone, renvoie un tableau à son rappel
}

Après que TOUS ces appels asynchrones soient terminés, je veux calculer le minimum sur tous les tableaux.

Comment puis-je attendre qu'ils soient tous terminés?

Ma seule idée en ce moment est d'avoir un tableau de booléens appelés done, et de définir done[i] à true dans la fonction de rappel ith, puis dire tant que (pas tous terminés) {}

éditer: Je suppose qu'une solution possible, mais laide, serait de modifier le tableau done dans chaque rappel, puis appeler une méthode si tous les autres done sont définis à partir de chaque rappel, donc le dernier rappel à terminer appellera la méthode de continuation.

204voto

jfriend00 Points 152127

Vous n'avez pas été très spécifique avec votre code, donc je vais créer un scénario. Disons que vous avez 10 appels ajax et que vous voulez accumuler les résultats de ces 10 appels ajax, puis quand ils sont tous terminés, vous voulez faire quelque chose. Vous pouvez le faire comme ceci en accumulant les données dans un tableau et en suivant quand le dernier a fini :

Compteur Manuel

var ajaxCallsRemaining = 10;
var returnedData = [];

for (var i = 0; i < 10; i++) {
    doAjax(whatever, function(response) {
        // poignée de succès de l'appel ajax

        // enregistrer la réponse
        returnedData.push(response);

        // voir si nous avons terminé avec le dernier appel ajax
        --ajaxCallsRemaining;
        if (ajaxCallsRemaining <= 0) {
            // toutes les données sont ici maintenant
            // parcourir les returnedData et faire tout le traitement 
            // que vous souhaitez juste ici
        }
    });
}

Note : la gestion des erreurs est importante ici (non affichée car spécifique à la façon dont vous faites vos appels ajax). Vous devrez réfléchir à la façon dont vous allez gérer le cas où un appel ajax ne se termine jamais, soit avec une erreur, soit reste bloqué pendant longtemps ou dépasse le délai après un certain temps.


jQuery Promesses

Ajout à ma réponse en 2014. De nos jours, les promesses sont souvent utilisées pour résoudre ce type de problème, puisque $.ajax() de jQuery renvoie déjà une promesse et $.when() vous permettra de savoir quand un groupe de promesses est résolu et collectera les résultats de retour pour vous :

var promises = [];
for (var i = 0; i < 10; i++) {
    promises.push($.ajax(...));
}
$.when.apply($, promises).then(function() {
    // les données renvoyées sont dans arguments[0][0], arguments[1][0], ... arguments[9][0]
    // vous pouvez les traiter ici
}, function() {
    // une erreur s'est produite
});

Promesses Standard ES6

Comme spécifié dans la réponse de kba : si vous avez un environnement avec des promesses natives intégrées (navigateur moderne ou node.js ou en utilisant la transcompilation babeljs ou en utilisant un polyfill de promesse), alors vous pouvez utiliser les promesses spécifiées par ES6. Voir ce tableau pour la prise en charge par les navigateurs. Les promesses sont prises en charge dans presque tous les navigateurs actuels, sauf IE.

Si doAjax() renvoie une promesse, vous pouvez faire ceci :

var promises = [];
for (var i = 0; i < 10; i++) {
    promises.push(doAjax(...));
}
Promise.all(promises).then(function() {
    // les données renvoyées sont dans arguments[0], arguments[1], ... arguments[n]
    // vous pouvez les traiter ici
}, function(err) {
    // une erreur s'est produite
});

Si vous avez besoin de transformer une opération asynchrone non promesse en une promesse, vous pouvez la "promisifier" comme ceci :

function doAjax(...) {
    return new Promise(function(resolve, reject) {
        someAsyncOperation(..., function(err, result) {
            if (err) return reject(err);
            resolve(result);
        });
    });
}

Et ensuite utilisez le schéma ci-dessus :

var promises = [];
for (var i = 0; i < 10; i++) {
    promises.push(doAjax(...));
}
Promise.all(promises).then(function() {
    // les données renvoyées sont dans arguments[0], arguments[1], ... arguments[n]
    // vous pouvez les traiter ici
}, function(err) {
    // une erreur s'est produite
});

Promesses Bluebird

Si vous utilisez une bibliothèque plus complète comme la bibliothèque de promesses Bluebird, alors elle contient quelques fonctions supplémentaires pour simplifier les choses :

 var doAjax = Promise.promisify(someAsync);
 var someData = [...]
 Promise.map(someData, doAjax).then(function(results) {
     // tous les résultats ajax sont ici
 }, function(err) {
     // une erreur quelconque
 });

18voto

kba Points 10874

En provenance de 2015: nous avons maintenant des promesses natives dans les navigateurs les plus récents (Edge 12, Firefox 40, Chrome 43, Safari 8, Opera 32 et le navigateur Android 4.4.4 et iOS Safari 8.4, mais pas Internet Explorer, Opera Mini et les anciennes versions d'Android).

Si nous voulons effectuer 10 actions asynchrones et être notifiés quand elles ont toutes terminé, nous pouvons utiliser le Promise.all natif, sans aucune bibliothèque externe :

function asyncAction(i) {
    return new Promise(function(resolve, reject) {
        var result = calculateResult();
        if (result.hasError()) {
            return reject(result.error);
        }
        return resolve(result);
    });
}

var promises = [];
for (var i=0; i < 10; i++) {
    promises.push(asyncAction(i));
}

Promise.all(promises).then(function AcceptHandler(results) {
    handleResults(results),
}, function ErrorHandler(error) {
    handleError(error);
});

10voto

Paul Points 6185

Vous pouvez utiliser l'objet Deferred de jQuery avec la méthode when.

deferredArray = [];
forloop {
    deferred = new $.Deferred();
    ajaxCall(function() {
      deferred.resolve();
    }
    deferredArray.push(deferred);
}

$.when(deferredArray, function() {
  //ce code est appelé après que tous les appels ajax sont terminés
});

9voto

Eugene Retunsky Points 7071

Vous pouvez l'émuler comme ceci :

  countDownLatch = {
     count: 0,
     check: function() {
         this.count--;
         if (this.count == 0) this.calculate();
     },
     calculate: function() {...}
  };

ensuite chaque appel asynchrone fait ceci :

countDownLatch.count++;

tandis que dans chaque rappel asynchrone à la fin de la méthode, vous ajoutez cette ligne :

countDownLatch.check();

En d'autres termes, vous émulez une fonctionnalité de verrouillage décompté.

6voto

philx_x Points 1

C'est la manière la plus propre à mon avis.

Promise.all

FetchAPI

(pour une raison quelconque, Array.map ne fonctionne pas à l'intérieur des fonctions .then pour moi. Mais vous pouvez utiliser un .forEach et [].concat() ou quelque chose de similaire)

Promise.all([
  fetch('/user/4'),
  fetch('/user/5'),
  fetch('/user/6'),
  fetch('/user/7'),
  fetch('/user/8')
]).then(responses => {
  return responses.map(response => {response.json()})
}).then((values) => {
  console.log(values);
})

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