2
votes

Asynchroniser / attendre à l'intérieur pour ... de vs map

J'essaie de comprendre pourquoi les promesses semblent fonctionner différemment dans for ... of et map () .

data est un tableau d'objets de la forme {app: MY_APP, link: MY_LINK} . J'essaye de le convertir sous la forme {app: MY_APP, status: true OR false}} . La fonction checkLink () est asynchrone car elle utilise node-fetch et renvoie essentiellement si le lien donné est 200. Utilisation de for ... of , J'obtiens l'objet désiré:

let objToSend = data.map(async obj => {
  return {
    app: obj.app,
    status: await checkLink(obj.link)
  };
});

// returns [ Promise { <pending> }, ...]

Mais en utilisant map , comme suit, j'obtiens un tableau de promesses en attente à la place. Toute aide serait appréciée:

let objToSend = [];
for (obj of data) {
  objToSend.push({ app: obj.app, status: await checkLink(obj.link) });
}

// returns [{app: MY_APP, status: true}, ...]

J'ai aussi essayé de faire Promise.all (objToSend) après le code map , mais il renvoie à la place Promise {}


3 commentaires

Votre rappel renvoie une promesse, donc sans surprise, il correspond à des promesses.


Une façon de penser à cela est que la boucle for est une boucle à l'intérieur d'une seule fonction. Cette fonction unique renvoie une seule promesse. Une map () est de nombreuses fonctions à l'intérieur d'une boucle. Chaque fonction renvoie une promesse donc vous vous retrouvez avec un tableau de promesses.


Ce tweet pourrait vous aider. twitter.com/ryanflorence/status/1264284487799595008?s=20


3 Réponses :


1
votes

Le await arrête toujours l'exécution de la fonction async dans laquelle il se trouve, cela ne fonctionne pas différemment dans les deux cas. Dans le dernier exemple cependant, vous appelez des fonctions asynchrones , et ces appels seront évalués en promesses, alors que dans votre premier exemple, tous ces attendent sont dans le même fonction asynchrone .

J'ai également essayé de faire Promise.all (objToSend) après le code de la carte, mais il renvoie à la place Promise {}

Oui, attendez cela et vous obtenez un tableau de résultats.


5 commentaires

Mais je devrais attendre le résultat du premier exemple alors non?


@mbj bien sûr, chaque fois que vous voulez utiliser le résultat d'un appel à fonction asynchrone , vous devez attendre que certains quand ... et que awaut doit alors être à l'intérieur d'une fonction asynchrone elle-même.


Bien, mais dans le cas de la boucle for, je n'ai pas à résoudre de promesses. Je comprends que c'est une promesse (contre plusieurs), mais je ne comprends pas pourquoi je n'ai pas à la résoudre.


Bien sûr, vous devez. Cette boucle for doit également être à l'intérieur d'une fonction async , et l'appel qui aboutit à une promesse.


Donc vous dites qu'il devrait y avoir une ligne comme wait objToSend dans le premier cas?



1
votes

Fonctions async toujours renvoie Promises . Dans votre fonction de carte, lorsque le rappel renvoie des promesses, un tableau de promesses est créé.

Pour le convertir à votre format, utilisez-le avec Promise.all () :

(async()=>{
    let objToSend = await Promise.all(data.map(async obj => {
        return {
            app: obj.app,
            status: await checkLink(obj.link)
        };
    }));
    console.log(objToSend) //[{app: MY_APP, status: true}, ...]
})()


0 commentaires

2
votes

Je vais laisser l'explication aux autres réponses, mais je veux juste souligner qu'il y a aussi une différence de performance.

Votre première solution attend à chaque itération que la promesse se résout. N'appelez checkLink que si le précédent est résolu. C'est une solution séquentielle.

const sleep = async ms => new Promise(resolve => setTimeout(resolve, ms));

async function checkLink(link) {
  await sleep(Math.random() * 1000 + 500); // sleep between 500 and 1500 ms
  console.log(link);
  return `status #${link}`;
}

(async function () {
  const data = new Array(5).fill().map((_, index) => ({ app: "app", link: index }));
  let objToSend;
  
  console.log("solution #1");
  objToSend = [];
  for (let obj of data) {
    objToSend.push({ app: obj.app, status: await checkLink(obj.link) });
  }
  console.log(objToSend);

  console.log("==============================================");
  
  console.log("solution #2");
  objToSend = await Promise.all(data.map(async obj => {
    return {
      app: obj.app,
      status: await checkLink(obj.link)
    };
  }));
  console.log(objToSend);
})();

La deuxième solution itère sur tous les éléments et envoie des requêtes, sans attendre la résolution des promesses (pour cette raison, un tableau de promesses est renvoyé) . Si vous attendez que toutes les promesses soient résolues, vous trouvez que cette solution est beaucoup plus rapide, car les requêtes sont envoyées en parallèle.

check link1 => check link2 => check link3 => wait for all

check link1 => wait => check link2 => wait => check link3 => wait

Dans l'extrait de code, la première solution prend 500/1500 * 5 = 2500/7500 entre 2500 et 7500 ms. Alors que la deuxième solution prend entre 500 et 1500 ms (selon la valeur la plus lente à résoudre).


0 commentaires