web-dev-qa-db-fra.com

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

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

forloop {
    //async call, returns an array to its callback
}

Une fois que TOUS ces appels asynchrones sont terminés, je souhaite calculer le minimum sur tous les tableaux.

Comment puis-je les attendre tous?

Ma seule idée à l’heure actuelle est d’avoir un tableau de booléens appelé done et de mettre done (i) à true dans la fonction de rappel, puis de dire while (tous ne sont pas terminés) {}

edit: Je suppose qu’une solution possible, mais laide, serait d’éditer le tableau done à chaque rappel, puis d’appeler une méthode si toutes les autres actions effectuées sont définies à partir de chaque rappel. Ainsi, le dernier rappel à terminer appellera la méthode continue.

Merci d'avance.

91
codersarepeople

Vous n'avez pas été très précis avec votre code, je vais donc inventer un scénario. Supposons que vous avez 10 appels ajax et que vous souhaitez accumuler les résultats de ces 10 appels ajax, puis que vous souhaitez faire quelque chose une fois qu'ils sont tous terminés. Vous pouvez le faire comme ceci en accumulant les données dans un tableau et en gardant une trace de la fin du dernier:

Compteur manuel

var ajaxCallsRemaining = 10;
var returnedData = [];

for (var i = 0; i < 10; i++) {
    doAjax(whatever, function(response) {
        // success handler from the ajax call

        // save response
        returnedData.Push(response);

        // see if we're done with the last ajax call
        --ajaxCallsRemaining;
        if (ajaxCallsRemaining <= 0) {
            // all data is here now
            // look through the returnedData and do whatever processing 
            // you want on it right here
        }
    });
}

Remarque: la gestion des erreurs est importante ici (non indiquée car elle est spécifique à la manière dont vous passez vos appels ajax). Vous voudrez peut-être réfléchir à la manière dont vous allez gérer le cas où un appel ajax ne se termine jamais, soit avec une erreur, soit pendant une longue période ou après une longue période.


jQuery Promises

Ajout à ma réponse en 2014. Ces jours-ci, les promesses sont souvent utilisées pour résoudre ce type de problème, car la $.ajax() de jQuery renvoie déjà une promesse et la $.when() vous avertira quand un groupe de promesses sont tous résolus et collecteront les résultats de retour pour vous:

var promises = [];
for (var i = 0; i < 10; i++) {
    promises.Push($.ajax(...));
}
$.when.apply($, promises).then(function() {
    // returned data is in arguments[0][0], arguments[1][0], ... arguments[9][0]
    // you can process it here
}, function() {
    // error occurred
});

ES6 Standard Promises

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 babeljs transpile ou en utilisant un polyfill de promesse), vous pouvez utiliser des promesses spécifiées par ES6 . Voir ce tablea pour la prise en charge du navigateur. Les promesses sont prises en charge dans presque tous les navigateurs actuels, sauf IE.

Si doAjax() renvoie une promesse, vous pouvez alors procéder comme suit:

var promises = [];
for (var i = 0; i < 10; i++) {
    promises.Push(doAjax(...));
}
Promise.all(promises).then(function() {
    // returned data is in arguments[0], arguments[1], ... arguments[n]
    // you can process it here
}, function(err) {
    // error occurred
});

Si vous devez créer une opération asynchrone non promise en une opération qui retourne une promesse, vous pouvez la "promisifier" comme suit:

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

Et utilisez ensuite le modèle ci-dessus:

var promises = [];
for (var i = 0; i < 10; i++) {
    promises.Push(doAjax(...));
}
Promise.all(promises).then(function() {
    // returned data is in arguments[0], arguments[1], ... arguments[n]
    // you can process it here
}, function(err) {
    // error occurred
});

Bluebird Promises

Si vous utilisez une bibliothèque plus riche en fonctionnalités, telle que bibliothèque de promesses Bluebird , des fonctions supplémentaires ont été intégrées pour rendre cela plus facile:

 var doAjax = Promise.promisify(someAsync);
 var someData = [...]
 Promise.map(someData, doAjax).then(function(results) {
     // all ajax results here
 }, function(err) {
     // some error here
 });
183
jfriend00

Enregistrement à partir de 2015: Nous avons maintenant promesses natives dans navigateur le plus récent (Edge 12, Firefox 40, Chrome 43, Safari 8, Opera 32 et Android navigateur 4.4.4 et iOS Safari 8.4, mais pas Internet Explorer, Opera Mini et versions antérieures d'Android).

Si nous voulons effectuer 10 actions asynchrones et recevoir une notification lorsqu'elles ont toutes terminé, nous pouvons utiliser le natif Promise.all , 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);
});
14
kba

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() {
  //this code is called after all the ajax calls are done
});
10
Paul

Vous pouvez l'imiter comme ceci:

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

alors chaque appel asynchrone fait ceci:

countDownLatch.count++;

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

countDownLatch.check();

En d'autres termes, vous émulez une fonctionnalité de compte à rebours.

9
Eugene Retunsky

C'est la manière la plus soignée à mon avis.

Promise.all

FetchAPI

(pour une raison quelconque, Array.map ne fonctionne pas dans les 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);
})
5
philx_x

Utilisez une bibliothèque de flux de contrôle comme after

after.map(array, function (value, done) {
    // do something async
    setTimeout(function () {
        // do something with the value
        done(null, value * 2)
    }, 10)
}, function (err, mappedArray) {
    // all done, continue here
    console.log(mappedArray)
})
1
Raynos