web-dev-qa-db-ja.com

async / awaitを使用したJavaScript配列.reduce

以下のように、async/awaitを.reduce()に組み込む際にいくつかの問題があるようです:

_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)
_

dataオブジェクトが記録されますbefore the _this.store_ completes ...

非同期ループで_Promise.all_を利用できることは知っていますが、それは.reduce()にも当てはまりますか?

37
benhowdle89

問題は、アキュムレーターの値が約束であるということです-それらはasync functionsの戻り値です。逐次評価(および最後の反復を除くすべて)を取得するには、以下を使用する必要があります。

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

とは言っても、async/awaitの場合、一般的に 配列反復メソッドの代わりにプレーンループを使用する をお勧めします。

71
Bergi

私はベルギの答えが好きです、それは正しい道だと思います。

私は Awaity.js と呼ばれる私のライブラリにも言及したいと思います

これにより、async / awaitreducemap、_filterなどの関数を簡単に使用できます。

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

Map/reduceイテレータブロック全体を独自のPromise.resolveにラップし、完了するのを待つことができます。ただし、問題は、アキュムレータに各反復で予想される結果のデータ/オブジェクトが含まれていないことです。内部async/await/Promiseチェーンにより、アキュムレーターは、ストアへの呼び出しの前にawaitキーワードを使用しているにもかかわらず、まだ解決されていない実際のPromiseそのものになります(これにより、反復が実際には行われないと思われる可能性があります)その呼び出しが完了し、アキュムレーターが更新されるまで戻ります。

これは最もエレガントな解決策ではありませんが、あなたが持っている1つのオプションは、適切なバインディングと突然変異ができるように、スコープ外にdataオブジェクト変数を移動し、それをletとして割り当てることです発生する。次に、async/await/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