https://zenn.dev/sora_kumo/articles/612ca66c68ff52

JavaScript の forEach は非同期ではない

時々ネットの解説記事で forEach は非同期だという解説を見かけますがこれは間違っています。実際の処理は逐次関数をコールバックしていくだけなので、普通に同期で動いています。コールバック中に非同期処理を使って待機動作を行っていないので、バラバラに動いているように見えるだけなのです。

検証用の非同期処理を作成

ランダムに 1000ms 以内の時間を待機して文字列を出力する関数です。TypeScript になっているので、JavaScript で実行したい場合は型定義を外してください。

const f = (value: string) => {
  return new Promise<void>((resolve) =>
    setTimeout(() => {
      console.log(value);
      resolve();
    }, Math.random() * 1000))
  );
};

普通に forEach で非同期処理を呼び出した場合

["A", "B", "C", "D", "E"].forEach((v) => {
  f(v);
});
console.log("終了");

終了と表示した A~E がランダムに一気に出力されます。何も同期していません。

非同期関数を forEach に設定した場合

["A", "B", "C", "D", "E"].forEach(async (v) => {
  await f(v);
});
console.log("終了");

コールバック関数を非同期にしても、結果は変わりません。非同期になるタイミングが異なるだけです。

Promise.all と map の組み合わせ

よくあるヤツです。

const main = async () => {
  await Promise.all(["A", "B", "C", "D", "E"].map((v) => f(v)));
  console.log("終了");
};
main();

一応、終了が最後に来るようになりました。ただ、逐次実行はされていません。

for を使う

const main = async () => {
  for (const i of ["A", "B", "C", "D", "E"]) await f(i);
  console.log("終了");
};
main();

逐次実行になりました。動作としてはこれで解決です。