javascriptのforEachは順番に(同期的に)実行される
現在(2024/04/10)、「javascript forEach 順番」で検索すると一番上に表示されるのがこの記事で、forEachに渡した関数は同期的に処理されないという内容なのですが、全体的に間違っていると思われます。
一度チーム内でforEach
って順番に実行されないんだね?となってしまったこともあり、この記事でまとめてみることにしました。
元記事へのコメント形式なので、そんなの当たり前だろ!という方はすみません!
まず、公式のforEachの説明を読んでみる
forEach() メソッドは反復処理メソッドです。指定された関数 callbackFn を配列に含まれる各要素に対して一度ずつ、昇順で呼び出します
同期処理のようですね
❌ forEachに渡している関数は即時関数
これは元記事のコメントでも指摘されていることですが...
まず、いわゆる即時関数(IIFE)はこちらになります。
定義されると同時に実行される関数のことですね
(function () {
console.log("hello!")
})();
一方、forEachはこちらになります。
forEachに渡しているコールバック関数((v) => console.log(v)
)はプログラムの実行時に定義されますが、定義と同時に実行される(呼び出される)わけではなく、イテレーションの度に呼び出されることになります
なので、即時関数ではないですね
array.forEach((v) => console.log(v));
❌ 即時関数はjavascriptの言語特性上、平行で実行される
(漢字ミスはさておき)javascriptで並行処理が行われるのは、promise
やasync/await
構文によって実行環境の非同期APIに処理を投げている場合だけです。
即時関数だからといって並行に実行されたりはしません。
では、元記事の「forEachを使うと、想定していた結果にならない」というのは?
これはおそらくforEach
に渡したコールバック関数内ではawait
がうまく動作しないという話だと思います。
const myArray = [1, 2, 3, 4, 5];
async function asyncFunction(item) {
return new Promise((resolve) => {
setTimeout(() => {
console.log(`Processing ${item}`);
resolve();
}, Math.random() * 1000);
});
}
async function processArray() {
myArray.forEach(async (item) => {
await asyncFunction(item);
});
}
processArray().then(() => {
console.log("終了");
})
// Output: 順番にならない&「終了」が先に来てしまう!
// 終了
// Processing 3
// Processing 5
// Processing 4
// Processing 1
// Processing 2
forEach
に渡されたasyncコールバック関数は同期的に(順番に)実行されますが、forEach
にはawaitでPromiseの履行を待つ機能がないため、asyncFunction()
が完了するのを待たずに次のループに移ってしまい、結果的に期待するような動作にはなりません。
async/await
したい場合は、for
やfor...of
を使うのが一般的です。
async function processArray() {
for (const item of myArray) {
await asyncFunction(item);
}
}
// Output
// Processing 1
// Processing 2
// Processing 3
// Processing 4
// Processing 5
// 終了
また、arrayを順番通りに実行する必要がない場合は、Promise.all
とmap
で並行処理にすることもできます。
async function processArray() {
await Promise.all(
myArray.map((item) => asyncFunction(item))
)
}
// Output
// Processing 4
// Processing 3
// Processing 5
// Processing 1
// Processing 2
// 終了
結論
array.forEach
はfor
と同様に同期的に実行される関数なので、処理は順番に行われます。
ただし、非同期処理を書くときはforEach
内ではawait
が動作しないため、for
文やPromise.all
で書く必要があります。
参考文献
- https://blog.devgenius.io/understanding-why-foreach-does-not-support-await-c79d085baba4
- Software Design 2023年9月号 JavaScript非同期処理の疑問を解き明かす
Discussion