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 の方で何とかしました。
コメント
コメントを投稿