forEach で break / continue が使えない理由
forEach は「配列の各要素に対して“関数を呼び出す”」高階メソッドです。ここが重要です:forEach は“自分でループを制御していない”ため、コールバック内から break や continue を使って“外側のループ本体”を止めたりスキップしたりできません。コールバックの return は「その要素の処理を終える」だけで、次要素の呼び出しは続行されます。
実際の挙動を例で確認する
break は構文エラーになる
[1, 2, 3].forEach(n => {
if (n === 2) break; // SyntaxError: Illegal break statement
});
JavaScriptforEach の中に「ループ構文」は存在しないので、break は使えません(構文上の制約)。
continue の代わりに return は“その要素だけ”スキップ
[1, 2, 3].forEach(n => {
if (n === 2) return; // この要素の処理を終える(他要素は続く)
console.log(n);
});
// 出力: 1, 3
JavaScriptここが重要です:return は“コールバックの終了”であり、ループ全体の制御ではありません。早期終了したい意図には合いません。
途中で完全に止めたいなら for / for…of を使う
for (const n of [1, 2, 3]) {
if (n === 2) break; // 正しく止まる
console.log(n);
}
// 出力: 1
JavaScriptfor/for…of は“言語のループ構文”なので、break/continue が使えます。
設計上の背景(なぜ止められないのか)
forEach の役割は“副作用を順に実行する”こと
forEach は「全要素に副作用を適用する」ためのメソッドです。API設計上、“全件を処理する”前提なので、途中停止のためのインターフェース(break/continue 相当)を持ちません。コールバックの戻り値も無視されます。
ループの“制御”はメソッドの外側にある
forEach は内部でイテレーションを完結させます。コールバックは“呼び出されるだけ”で、外側の制御フローへ影響を返しません。だから、途中停止やスキップの制御をコールバックから注入することができません。
目的別の正しい書き方(代替手段)
途中で止めたい(最初の一致で終了したい)
// some / find を使う
const hasTarget = [1, 2, 3].some(n => n === 2); // true(見つかったら終了)
const hit = [1, 2, 3].find(n => n % 2 === 0); // 2(最初の一致だけ)
// ループで止めたいなら for...of
let found = undefined;
for (const n of [1, 2, 3]) {
if (n % 2 === 0) { found = n; break; }
}
JavaScriptここが重要です:存在判定なら some、要素取得なら find、柔軟制御なら for…of が最適です。
スキップしながら副作用したい
// 条件ガード+return(要素単位のスキップ)
[1, 0, 3].forEach(n => {
if (n === 0) return;
console.log(n);
});
// または filter で前処理してから forEach
[1, 0, 3].filter(n => n !== 0).forEach(n => console.log(n));
JavaScriptここが重要です:スキップは「前処理(filter)」に分離すると読みやすく、意図が明確になります。
非同期で順序どおりに処理/途中停止したい
// 逐次:for...of + await(途中停止も可能)
async function run(ids) {
for (const id of ids) {
const res = await fetch(`/api/${id}`);
if (!res.ok) break;
// …
}
}
// 並列:Promise.all(止めるより“結果で分岐”)
const promises = ids.map(id => fetch(`/api/${id}`));
const results = await Promise.all(promises);
JavaScriptここが重要です:forEach 内の await は待たれないため使わない。順次なら for…of、並列なら Promise.all。
よくある誤解と回避策(深掘り)
“例外で止める”のは避ける
[1, 2, 3].forEach(n => {
if (n === 2) throw new Error("stop"); // 止まるが、副作用的で扱いづらい
});
JavaScriptここが重要です:例外は“異常系”に使うべきもの。通常のフロー制御には不適切。素直に some/find/for…of に切り替えます。
forEach で結果配列を作らない
forEach の中で外部配列に push すると「副作用の塊」になりがちです。変換は map、抽出は filter、集約は reduce を使うと、break/continue 不要で意図が明確になります。
const labels = products
.filter(p => p.stock > 0)
.map(p => `${p.name} (${p.price})`);
JavaScript疎配列はコールバックが“呼ばれない”
空スロット(穴)は forEach がスキップします。穴も含めて扱いたいなら、事前に正規化(Array.from や fill)してください。
まとめ
forEach は「全要素へ副作用を順に適用する」メソッドで、コールバック内から break/continue で“ループ自体”を制御することはできません。途中停止やスキップのニーズには、目的別に道具を選ぶのが最善です。存在判定は some、最初の一致は find、柔軟な停止・スキップは for/for…of、非同期は for…of+await または Promise.all。前処理(filter)でスキップを表現し、変換・抽出は map/filter/reduce に任せれば、初心者でも意図どおりに安全で読みやすいループが書けます。
