Processus asynchrone à l'intérieur d'un javascript pour boucle [dupliquer]

cette question a déjà une réponse ici:

j'exécute une boucle d'événements de la forme suivante:

var i;
var j = 10;
for (i = 0; i < j; i++) {

    asynchronousProcess(callbackFunction() {
        alert(i);
    });
}

j'essaie d'afficher une série d'alertes montrant l' Numéros 0 à 10. Le problème est qu'au moment où la fonction de rappel est déclenchée, la boucle a déjà traversé quelques itérations et affiche une valeur plus élevée de i . Des recommandations sur la façon d'y remédier?

77
demandé sur pushkin 2012-07-15 02:56:56

6 réponses

la boucle for s'exécute immédiatement jusqu'à son achèvement pendant que toutes vos opérations asynchrones commencent. Quand ils termineront quelque temps dans le futur et appelleront leurs callbacks, la valeur de votre variable d'index de boucle i sera à sa dernière valeur pour toutes les callbacks.

C'est parce que le for boucle ne pas attendre d'une opération asynchrone avant de passer à l'itération suivante de la boucle et parce que l'asynchrone les rappels sont appelés dans l'avenir. Ainsi, la boucle termine ses itérations et les callbacks sont appelés lorsque les opérations async sont terminées. En tant que tel, l'indice de boucle est "fait", et assis à sa valeur finale pour tous les rappels.

pour contourner cela, vous devez enregistrer l'index de boucle séparément pour chaque callback. En Javascript, la façon de le faire est de les saisir dans une fonction de fermeture. Cela peut être fait soit créer une fonction en ligne fermeture spécifiquement pour cet usage (le premier exemple ci-dessous) ou vous pouvez créer une fonction externe que vous passez l'index et le laisser maintenir l'index unique pour vous (deuxième exemple ci-dessous).

à partir de 2016, si vous avez une implémentation entièrement up-to-species6 de Javascript, vous pouvez également utiliser let pour définir la variable de boucle for et elle sera définie de manière unique pour chaque itération de la boucle for (troisième implémentation ci-dessous). Mais, notez qu'il s'agit d'une fonctionnalité de mise en œuvre tardive dans les implémentations ES6, vous devez donc vous assurer que votre environnement d'exécution supporte cette option.

utiliser .forEach () pour itérer car il crée sa propre fonction de fermeture

someArray.forEach(function(item, i) {
    asynchronousProcess(function(item) {
        console.log(i);
    });
});

créez votre propre fonction de fermeture en utilisant un IIFE

var j = 10;
for (var i = 0; i < j; i++) {
    (function(cntr) {
        // here the value of i was passed into as the argument cntr
        // and will be captured in this function closure so each
        // iteration of the loop can have it's own value
        asynchronousProcess(function() {
            console.log(cntr);
        });
    })(i);
}

créer ou modifier une fonction externe et la transmettre Variable

si vous pouvez modifier la fonction asynchronousProcess() , alors vous pouvez juste passer la valeur là - dedans et avoir la fonction asynchronousProcess() le cntr de nouveau au rappel comme ceci:

var j = 10;
for (var i = 0; i < j; i++) {
    asynchronousProcess(i, function(cntr) {
        console.log(cntr);
    });
}

utiliser ES6 let

si vous avez un environnement D'exécution Javascript qui supporte entièrement ES6, vous pouvez utiliser let dans votre boucle for comme ceci:

const j = 10;
for (let i = 0; i < j; i++) {
    asynchronousProcess(function() {
        console.log(i);
    });
}

let déclaré dans une déclaration de boucle for comme ceci créera une valeur unique de i pour chaque invocation de la boucle (ce qui est ce que vous voulez).

Serializing with promises and async / wait

si votre fonction async retourne une promesse, et que vous voulez sérialiser vos opérations async pour exécuter les unes après les autres au lieu d'en parallèle et que vous exécutez dans un environnement moderne environnement qui supporte async et await , alors vous avez plus d'options.

async function someFunction() {
    const j = 10;
    for (let i = 0; i < j; i++) {
        // wait for the promise to resolve before advancing the for loop
        await asynchronousProcess();
        console.log(i);
    }
}

cela permettra de s'assurer qu'un seul appel à asynchronousProcess() est en vol à la fois et que la boucle for n'avancera même pas jusqu'à ce que chacun soit terminé. C'est différent des schémas précédents qui ont tous exécuté vos opérations asynchrones en parallèle, donc cela dépend entièrement du design que vous voulez. Remarque: await fonctionne avec une promesse de sorte que votre fonction a retourner une promesse qui est résolue/rejetée lorsque l'opération asynchrone est terminée. En outre, notez que pour utiliser await , la fonction contenant doit être déclarée async .

137
répondu jfriend00 2018-01-19 07:02:20

une recommandation sur la façon de corriger cela?

plusieurs. Vous pouvez utiliser lier :

for (i = 0; i < j; i++) {
    asycronouseProcess(function (i) {
        alert(i);
    }.bind(null, i));
}

ou, si votre navigateur prend en charge let (il sera dans la prochaine version D'ECMAScript, mais Firefox le prend déjà en charge depuis un certain temps) vous pourriez avoir:

for (i = 0; i < j; i++) {
    let k = i;
    asycronouseProcess(function() {
        alert(k);
    });
}

ou, vous pouvez faire le travail de bind manuellement (au cas où le navigateur ne supporte pas, mais je dirais que vous pouvez mettre en œuvre un shim dans ce cas, il devrait être dans le lien ci-dessus):

for (i = 0; i < j; i++) {
    asycronouseProcess(function(i) {
        return function () {
            alert(i)
        }
    }(i));
}

je préfère habituellement let quand je peux l'utiliser (par exemple pour L'add-on de Firefox); sinon bind ou une fonction currying (qui n'a pas besoin d'un objet contextuel).

10
répondu ZER0 2012-07-14 23:59:46

async await est ici (ES7), donc vous pouvez faire ce genre de choses très facilement maintenant.

  var i;
  var j = 10;
  for (i = 0; i < j; i++) {
    await asycronouseProcess();
    alert(i);
  }

Rappelez-vous, cela ne fonctionne que si asycronouseProcess est de retour Promise

si asycronouseProcess n'est pas sous votre contrôle alors vous pouvez le faire retourner un Promise par vous-même comme ceci

function asyncProcess() {
  return new Promise((resolve, reject) => {
    asycronouseProcess(()=>{
      resolve();
    })
  })
}

remplacer ensuite cette ligne await asycronouseProcess(); par await asyncProcess();

Compréhension Promises avant même de regarder dans async await doit (Lire aussi à propos du support pour async await )

7
répondu Praveena 2017-06-12 04:43:01

var i = 0;
var length = 10;

function for1() {
  console.log(i);
  for2();
}

function for2() {
  if (i == length) {
    return false;
  }
  setTimeout(function() {
    i++;
    for1();
  }, 500);
}
for1();

voici un exemple d'approche fonctionnelle à ce qui est attendu ici.

1
répondu Black Mamba 2017-08-01 07:33:29

code JavaScript fonctionne sur un seul thread, de sorte que vous ne pouvez pas principalement bloquer d'attendre la première itération de boucle à compléter avant de commencer la prochaine sans affecter sérieusement la convivialité de la page.

la solution dépend de ce dont vous avez vraiment besoin. Si l'exemple est proche de ce dont vous avez besoin, la suggestion de @Simon de passer i à votre processus asynchrone est bonne.

0
répondu Eric J. 2012-07-14 23:11:02

ES2017: vous pouvez envelopper le code async dans une fonction(disons XHRPost) retournant une promesse( code Async dans la promesse).

appelle alors la fonction (XHRPost) à l'intérieur de la boucle for mais avec le mot-clé magique attente. :)

let http = new XMLHttpRequest();
let url = 'http://sumersin/forum.social.json';

function XHRpost(i) {
  return new Promise(function(resolve) {
    let params = 'id=nobot&%3Aoperation=social%3AcreateForumPost&subject=Demo' + i + '&message=Here%20is%20the%20Demo&_charset_=UTF-8';
    http.open('POST', url, true);
    http.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
    http.onreadystatechange = function() {
    console.log("Done " + i + "<<<<>>>>>" + http.readyState);
          if(http.readyState == 4){
              console.log('SUCCESS :',i);
              resolve();
          }
         }
    http.send(params);       
    });
 }
 
(async () => {
    for (let i = 1; i < 5; i++) {
        await XHRpost(i);
       }
})();
0
répondu Sumer 2018-08-18 01:29:50