JavaScript | 配列・オブジェクト:ループ処理 – break / continue の使えない理由(forEach)

JavaScript JavaScript
スポンサーリンク

forEach で break / continue が使えない理由

forEach は「配列の各要素に対して“関数を呼び出す”」高階メソッドです。ここが重要です:forEach は“自分でループを制御していない”ため、コールバック内から break や continue を使って“外側のループ本体”を止めたりスキップしたりできません。コールバックの return は「その要素の処理を終える」だけで、次要素の呼び出しは続行されます。


実際の挙動を例で確認する

break は構文エラーになる

[1, 2, 3].forEach(n => {
  if (n === 2) break; // SyntaxError: Illegal break statement
});
JavaScript

forEach の中に「ループ構文」は存在しないので、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
JavaScript

for/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 に任せれば、初心者でも意図どおりに安全で読みやすいループが書けます。

タイトルとURLをコピーしました