web-dev-qa-db-fra.com

Tableau JavaScript .reduce avec async / wait

Il semble y avoir des problèmes lors de l’incorporation de async/wait avec .reduce (), comme ceci:

const data = await bodies.reduce(async(accum, current, index) => {
  const methodName = methods[index]
  const method = this[methodName]
  if (methodName == 'foo') {
    current.cover = await this.store(current.cover, id)
    console.log(current)
    return {
      ...accum,
      ...current
    }
  }
  return {
    ...accum,
    ...method(current.data)
  }
}, {})
console.log(data)

L'objet data est enregistré avant le this.store Se termine ...

Je sais que vous pouvez utiliser Promise.all Avec des boucles asynchrones, mais cela s'applique-t-il à .reduce()?

37
benhowdle89

Le problème est que vos valeurs d'accumulateur sont des promesses - elles renvoient des valeurs de async functions. Pour obtenir une évaluation séquentielle (et même attendre la dernière itération), vous devez utiliser

const data = await array.reduce(async (accumP, current, index) => {
  const accum = await accumP;
  …
}, Promise.resolve(…));

Cela dit, pour async/await, je recommanderais en général de tiliser des boucles simples à la place des méthodes d'itération de tableaux , elles sont plus performantes et souvent plus simples.

71
Bergi

J'aime la réponse de Bergi, je pense que c'est la bonne façon de faire.

J'aimerais aussi mentionner une de mes bibliothèques, appelée Awaity.js

Ce qui vous permet d’utiliser facilement des fonctions telles que reduce, map & filter avec async / await:

import reduce from 'awaity/reduce';

const posts = await reduce([1,2,3], async (posts, id) => {

  const res = await fetch('/api/posts/' + id);
  const post = await res.json();

  return {
    ...posts,
    [id]: post
  };
}, {})

posts // { 1: { ... }, 2: { ... }, 3: { ... } }
3
Asaf Katz

Vous pouvez envelopper l'ensemble de votre carte/réduire le nombre de blocs d'itérateur dans leur propre Promise.resolve et attendre que cette opération soit terminée. Le problème, cependant, est que l'accumulateur ne contient pas les données/objets que vous attendez à chaque itération. En raison de la chaîne asynchrone/wait/Promise interne, l'accumulateur sera de véritables promesses qui doivent encore se résoudre malgré l'utilisation d'un mot clé wait avant votre appel au magasin (ce qui pourrait vous amener à penser que l'itération ne revenir jusqu'à ce que l'appel soit terminé et que l'accumulateur soit mis à jour.

Bien que ce ne soit pas la solution la plus élégante, vous avez la possibilité de déplacer votre variable d'objet data hors de la portée et de l'affecter sous la forme d'une let afin qu'une liaison et une mutation appropriées puissent se produire. Ensuite, mettez à jour cet objet de données à l'intérieur de votre itérateur lors de la résolution des appels async/wait/Promise.

/* allow the result object to be initialized outside of scope 
   rather than trying to spread results into your accumulator on iterations, 
   else your results will not be maintained as expected within the 
   internal async/await/Promise chain.
*/    
let data = {}; 

await Promise.resolve(bodies.reduce(async(accum, current, index) => {
  const methodName = methods[index]
  const method = this[methodName];
  if (methodName == 'foo') {
    // note: this extra Promise.resolve may not be entirely necessary
    const cover = await Promise.resolve(this.store(current.cover, id));
    current.cover = cover;
    console.log(current);
    data = {
      ...data,
      ...current,
    };
    return data;
  }
  data = {
    ...data,
    ...method(current.data)
  };
  return data;
}, {});
console.log(data);
1
Brandon K
export const addMultiTextData = async(data) => {
  const textData = await data.reduce(async(a, {
    currentObject,
    selectedValue
  }) => {
    const {
      error,
      errorMessage
    } = await validate(selectedValue, currentObject);
    return {
      ...await a,
      [currentObject.id]: {
        text: selectedValue,
        error,
        errorMessage
      }
    };
  }, {});
};
0
Rajesh Dalai