web-dev-qa-db-fra.com

Node.js - Attendez que plusieurs appels asynchrones se terminent avant de continuer dans le code

Donc, fondamentalement, j'ai une boucle for avec une fonction asynchrone. Le problème est que le programme continue juste après la boucle et je veux qu'il attende que toutes les fonctions asynchrones qui ont été appelées dans la boucle soient terminées avant que le code continue.

Dans mon code, "bar" est un tableau json avec d'autres tableaux json.

function write(bla) { // gets called one after another

  for(var url in bla) {
    asyncFunctionCall(url); // Executed about 50 times, it has to run parallel
  }
  // Wait for all called functions to finish before next stuff happens and
  // write gets called again.

}

for(var foo in bar) {
  // Here i parse the json array "foo" which is in the json array "bar"
  write(foo[bla]); // bla is an array of multiple urls.
}

L'appel de fonction asynchrone ressemble à ceci:

var request = require('request');

request(url, function (error, response, body) {
  if(typeof response !== 'undefined') {
    if((response.statusCode >= 400 && response.statusCode <= 451)
    || (response.statusCode >= 500 && response.statusCode <= 511))
      return true;
    return false;
  }
  return false;
});
8
Rxchard

Le moyen le plus simple ici est d'utiliser des promesses, directement ou via la syntaxe async/await. Dans ce cas, probablement directement.

Tout d'abord, vous devez faire en sorte que asyncFunctionCall retourne une promesse. Il semble que vous renvoyiez toujours un booléen, dans ce cas, nous résoudrons toujours la promesse:

function asyncFunctionCall(url) {
  return new Promise(resolve => {
    request(url, function (error, response, body) {
      if(typeof response !== 'undefined') {
        if((response.statusCode >= 400 && response.statusCode <= 451)
        || (response.statusCode >= 500 && response.statusCode <= 511)) {
          resolve(true);
          return;
        }
      }
      resolve(false);
    });
  });
}

Ensuite, créez un tableau de vos promesses et utilisez Promise.all d'attendre que tous se terminent. Ces appels s'exécutent en parallèle :

function write(bla) { // gets called one after another
  const promises = [];
  for(var url in bla) {
    promises.Push(asyncFunctionCall(url)); // Executed about 50 times.
  }
  return Promise.all(promises);
}

Ensuite, vous pouvez créer une chaîne de toutes les promesses à partir de write afin qu'elles s'exécutent en série:

let p = Promise.resolve();
for (const foo in bar) { // <== Notice `const`
  // See "Edit" below
  p = p.then(() => {
    // Here i parse the json object "foo" in the json array "bar"
    // bla is an array of multiple urls.
    return write(foo[bla]));
  });
}

Notez qu'il est important que vous utilisiez const ou let, pas var, pour foo dans cette boucle, car le rappel then se ferme par-dessus; voir réponses à cette question pour savoir pourquoi const et let font que cela fonctionne.

Chaque appel à write ne sera effectué que lorsque le travail précédent sera terminé.

Attendez ensuite la fin du processus:

p.then(() => {
  // All done
});

Vous n'avez rien montré en utilisant les booléens des requêtes de write, mais ils sont disponibles (sous forme de tableau) comme valeur de résolution de la promesse de write.


La deuxième partie du processus, où nous appelons write, peut également être écrite dans une fonction async qui peut rendre le flux logique plus clair:

async function doTheWrites() {
  for (const foo in bar) {
    // Here i parse the json object "foo" in the json array "bar"
    // bla is an array of multiple urls.
    await write(foo[bla]);
  }
}

Ensuite, le processus global:

doTheWrites().then(() => {
  // All done
});

... ou si c'est aussi dans une fonction async:

await doTheWrites();
9
T.J. Crowder

Rendez les fonctions asynchrones et attendez les appels:

async function write(foo) {
   for(const url of foo) {
      await asyncFunctionCall(url);
   }  
}

(async function() {
   for(const foo of bar) {
     await  write(foo);
   }
})()

Cela exécutera une demande après l'autre. Pour les exécuter en parallèle, utilisez Promise.all:

const write = foo => Promise.all(foo.map(asyncFunctionCall));

Promise.all(bar.map(write))
  .then(() => console.log("all done"));
2
Jonas Wilms