forEach と async function
forEach と async function:
先日、API Gateway + Lambda な環境で JavaScript のコード内で
配列に対して返り値を必要としない処理を行う場合、
例えば、以下のコードを実行します。
これは
対策方法は、処理を順番に行いたい場合と、並列に行いたい場合で異なります。
配列の順番に処理が実行されています。
インデックスが不要な場合は
並列に処理が実行されています。
先日、API Gateway + Lambda な環境で JavaScript のコード内で
Array#forEach
に渡した async function
が実行されずにハマってたので書いておきます。
TL;DR
-
Array#forEach
にasync function
は渡さない方がいい -
Promise.all
とArray#map
を組み合わせると良さげ
現象
for
文は冗長な気がして、Array#forEach
や Array#map
を使いたい今日この頃。配列に対して返り値を必要としない処理を行う場合、
Array#forEach
を利用したくなりますが、 async function
を渡した場合にうまくいきません。例えば、以下のコードを実行します。
foreach0.ts
const sleep = (ms: number) => new Promise(resolve => setTimeout(resolve, ms)); const asyncFunc = async (sec: number, idx: number) => { await sleep(sec * 1000); console.log(`sleep[${idx}]: ${sec} sec`); }; const a = [4, 3, 2, 1]; a.forEach(asyncFunc); console.log('finish');
$ ts-node foreach0.ts finish sleep[3]: 1sec sleep[2]: 2sec sleep[1]: 3sec sleep[0]: 4sec
forEach
内の処理が終わる前に、finish が表示されています。これは
forEach
が返り値を全く考慮しない、たとえ Promise
が帰って来ようが処理を進めてしまうのが原因です。
対策
対策方法は、処理を順番に行いたい場合と、並列に行いたい場合で異なります。- 順番に行いたい場合
for
文を使いましょう。foreach1.ts
type AsyncFunc<T> = (x: T, i: number) => Promise<void>; const asyncForEach = async <T>(ary: T[], fn: AsyncFunc<T>) => { for (let i = 0; i < ary.length; i = i + 1) { await fn(ary[i], i); } }; const main = async () => { const a = [4, 3, 2, 1]; await asyncForEach(a, asyncFunc); console.log('finish'); }; main();
$ ts-node foreach1.ts sleep[0]: 4 sec sleep[1]: 3 sec sleep[2]: 2 sec sleep[3]: 1 sec finish
インデックスが不要な場合は
for..of
文を利用してもいいでしょう。- 並列に行いたい場合
Promise.all
と Array#map
を組み合わせます。foreach2.ts
const parallelForEach = <T>(ary: T[], fn: AsyncFunc<T>) => ( Promise.all(ary.map((x, i) => fn(x, i))) ); const main = async () => { const a = [4, 3, 2, 1]; await parallelForEach(a, asyncFunc); console.log('finish'); }; main();
$ ts-node foreach2.ts sleep[3]: 1 sec sleep[2]: 2 sec sleep[1]: 3 sec sleep[0]: 4 sec finish
まとめ
-
Array#forEach
はasync function
を待ってくれない - 順番に処理したい場合は
for
文を使う - 並列に処理したい場合は
Promise.all
とArray#map
を組み合わせて使う
Promise.all
と Array#map
の方で何とかしました。
コメント
コメントを投稿