LoginSignup
2
1

More than 5 years have passed since last update.

forEach と async function

Posted at

先日、API Gateway + Lambda な環境で JavaScript のコード内で Array#forEach に渡した async function が実行されずにハマってたので書いておきます。

TL;DR

  • Array#forEachasync function は渡さない方がいい
  • Promise.allArray#map を組み合わせると良さげ

現象

for 文は冗長な気がして、Array#forEachArray#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 が帰って来ようが処理を進めてしまうのが原因です。

対策

対策方法は、処理を順番に行いたい場合と、並列に行いたい場合で異なります。

  1. 順番に行いたい場合

配列の要素を順番に処理したい場合は 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 文を利用してもいいでしょう。

  1. 並列に行いたい場合

配列の順番に関係なく、並列に処理したい場合は Promise.allArray#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#forEachasync function を待ってくれない
  • 順番に処理したい場合は for 文を使う
  • 並列に処理したい場合は Promise.allArray#map を組み合わせて使う

ちなみにハマった件では Promise.allArray#map の方で何とかしました。

2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1