読み込み中...

JavaScriptのforEachでreturnを活用する6つのテクニック

JavaScriptのforEachでreturnを使いこなすイメージ JS
この記事は約24分で読めます。

【サイト内のコードはご自由に個人利用・商用利用いただけます】

この記事では、プログラムの基礎知識を前提に話を進めています。

説明のためのコードや、サンプルコードもありますので、もちろん初心者でも理解できるように表現してあります。

本記事のサンプルコードを活用して機能追加、目的を達成できるように作ってありますので、是非ご活用ください。

※この記事は、一般的にプロフェッショナルの指標とされる『実務経験10,000時間以上』を満たす現役のプログラマチームによって監修されています。

※Japanシーモアは、常に解説内容のわかりやすさや記事の品質に注力しております。不具合、分かりにくい説明や不適切な表現、動かないコードなど気になることがございましたら、記事の品質向上の為にお問い合わせフォームにてご共有いただけますと幸いです。
(送信された情報は、プライバシーポリシーのもと、厳正に取扱い、処分させていただきます。)

はじめに

JavaScriptのforEachreturnを使うと、現在のコールバック関数だけを抜けて次の要素へ進みます。breakのようにループ処理全体は止まらないため、途中終了が必要ならsomeeveryfind、通常のfor...ofを選びます。

forEachはログ出力、DOM更新、既存配列への追加など、副作用を伴う処理に向きますし、ここがポイントです。新しい配列ならmapfilter、集約ならreduceが読みやすい場面も多いです。

動作確認環境
  • JavaScript: ECMAScript 2024相当
  • Node.js: 22.0.0
  • ブラウザ: Google Chrome 126

公式仕様に近い確認先として、配列の反復処理はMDNのArray.prototype.forEach()、新しい配列を返す処理はMDNのArray.prototype.map()が参考になります。非同期処理ではMDNのPromise.all()も確認しておくと判断しやすくなります。

const numbers = [1, 2, 3, 4, 5];

numbers.forEach((num) => {
  if (num === 3) {
    return;
  }
  console.log(num);
});

結果: 期待される出力は1245です。3の処理だけが飛ばされ、forEachループ処理自体は続きますし、ここがポイントです。

forEach内でのreturnの基本

forEachreturnコールバック関数から戻るだけです。return falseでも全体は止まらず、次の要素で同じコールバック関数が呼ばれます。

特定の要素を処理対象から外すなら、条件に合わない値で早めにreturnするとネストを浅く保てます。途中で探索を止める用途ならfind、真偽判定ならsomeevery、細かい制御ならforfor...ofが合いるのが基本です。

returnで現在の要素だけをスキップする

偶数だけを出力したいときは、奇数でreturnするのが基本です。処理本体を深いifに入れずに済み、条件が増えても読みやすく保てます。

const numbers = [1, 2, 3, 4, 5, 6];

numbers.forEach((num) => {
  if (num % 2 !== 0) {
    return;
  }

  console.log(num);
});

結果: 期待される出力は246です。奇数のときだけreturnで現在のコールバック関数を終え、偶数のときにconsole.logへ進みますが、これは押さえたい点です。

returnは値を返す目的ではなく、以降の処理を読ませない分岐です。forEachは戻り値を使わないため、return numreturn trueも外側には渡りません。

returnでforEach全体は止まらない

returnを見つけた時点でループ処理が終わる、と考えると誤りです。制御対象は外側ではなく、各要素ごとのコールバック関数です。

const numbers = [1, 2, 3, 4, 5];

numbers.forEach((num) => {
  console.log(num);

  if (num === 3) {
    return;
  }
});

結果: 期待される出力は12345です。3returnしても、その後の45にも処理が回ります。

「条件に合う値が見つかったら終わり」ならforEachを避けます。値を探すならfind、存在判定だけならsomeが合いるのが目安です。

💡 Tips: forEach内のreturnは「その要素の残り処理を読ませない」ための書き方です。探索の途中終了、変換結果の生成、条件抽出を同時に担わせると意図がぼやけますし、ここがポイントです。
処理主な用途戻り値途中終了向く場面
forEach各要素への副作用undefined不可ログ、DOM更新、外部配列への追加
return現在の関数を抜ける関数次第forEach全体は不可条件外の要素をスキップ
for細かい反復制御なしインデックス制御、逆順処理
for...of値を順番に処理なしbreakcontinueを使う処理
map変換新しい配列不可全要素を別の値へ変える
filter抽出新しい配列不可条件に合う値だけ残す
reduce集約任意の値不可合計、分類、オブジェクト化
someいずれか判定boolean条件を満たす値の存在確認
every全件判定boolean全要素が条件を満たすか確認
find最初の一致取得要素またはundefinedID検索、条件一致の取得
findIndex最初の位置取得数値一致した位置を使う処理
Object.keysキー配列化配列不可オブジェクトのキー巡回
Object.values値配列化配列不可値だけを集計
Object.entriesキーと値の配列化配列不可キーと値を同時に扱う
push末尾追加配列長対象外forEach内で結果配列へ追加
console.log確認出力undefined対象外処理の流れを確認
try例外監視なし対象外危険な処理を囲む
catch例外処理なし対象外エラー時の復旧
throw例外発生なし不正値を明示する
async非同期関数Promise対象外非同期処理を関数化
awaitPromise待機解決値対象外async関数内の順序制御
Promise.all並列待機配列拒否時に中断複数の非同期処理を待つ
setTimeout遅延処理タイマーID対象外非同期の例示
typeof型確認文字列対象外数値や文字列の判定
Number.isFinite有限数判定boolean対象外数値入力の検査
Array.isArray配列判定boolean対象外入力値の検査
includes包含判定boolean対象外除外リストの確認
trim空白除去文字列対象外フォーム値の整形
toUpperCase大文字化文字列対象外表示用文字列の加工
structuredClone深いコピー複製値対象外元データを壊さない加工

早見表の要点は、forEachが戻り値を組み立てる道具ではない点です。目的を副作用、変換、抽出、集約、探索に分けると、選ぶループ処理も決まります。

forEachと他のループメソッドとの比較

forEachと他の配列メソッドは、返り値と途中終了の可否で整理できます。アロー関数の見た目は似ても、returnの扱いはメソッドごとに違いるのがポイントです。

mapでは戻り値が新しい配列の要素になり、filterでは真偽値が採用可否を決めますが、これは押さえたい点です。forEachでは戻り値が捨てられるため、結果配列を作る目的では遠回りです。

forEachとfor文の違い

for文は初期化、条件、更新を自分で書くため、開始位置、終了条件、増分、逆順、途中終了を細かく扱えます。全件に同じ処理をするだけならforEachでも十分です。

const items = ['a', 'b', 'c', 'd'];

for (let i = 0; i < items.length; i++) {
  if (items[i] === 'c') {
    break;
  }

  console.log(items[i]);
}

items.forEach((item) => {
  if (item === 'c') {
    return;
  }

  console.log(item);
});

結果: 期待される出力は、for文側がabで止まり、forEach側がabdを出するのが一般的です。breakreturnの違いが表れます。

途中で止めたい、偶数番目だけ処理したい、後ろから読むといった条件があるならfor文が扱いやすいです。

forEachとmapの違い

mapは新しい配列を返すため、変換の意図がコードに残ります。forEachで外側へpushするより、returnした値が結果になるmapのほうが伝わりやすいです。

const prices = [100, 200, 300];

const withTaxByForEach = [];
prices.forEach((price) => {
  withTaxByForEach.push(price * 1.1);
});

const withTaxByMap = prices.map((price) => {
  return price * 1.1;
});

console.log(withTaxByForEach);
console.log(withTaxByMap);

結果: 期待される出力はいずれも[110.00000000000001, 220.00000000000003, 330]に近い配列です。小数の丸めは別問題として、変換目的ならmapreturnが結果配列を作ります。

forEachは外側のwithTaxByForEachを変更しています。副作用が悪いわけではありませんが、変換なのか追加なのかを区別すると追いやすくなるのが現実的です。

someやfindで途中終了を表す

途中終了が必要な判定では、somefindが自然な候補です。someコールバック関数trueを返した時点で止まり、findは条件に合う最初の要素を返するのが目安です。

const users = [
  { id: 1, active: false },
  { id: 2, active: true },
  { id: 3, active: true },
];

const hasActiveUser = users.some((user) => {
  return user.active;
});

const firstActiveUser = users.find((user) => {
  return user.active;
});

console.log(hasActiveUser);
console.log(firstActiveUser);

結果: 期待される出力はtrue{ id: 2, active: true }です。探索の意図がsomefindに現れ、forEachでフラグ変数を管理する必要がありません。

変換ならmap、抽出ならfilter、合計や分類ならreduceを選ぶと、returnの意味がメソッド名と結びつきます。アロー関数の短い構文も、目的が明確なときに効きます。

⚠️ 注意: forEachの戻り値は常にundefinedです。const result = array.forEach(...)の形で結果配列を受け取ろうとすると、意図と違う値になると整理できると整理できるのがポイントです。

関連する配列メソッドは、内部記事のJavaScriptのforEach・mapの使い分けも参照できます。イベント処理では、JavaScriptイベントの使い方イベントハンドラの実践例も関連します。

実践的なforEachの使用例

forEachで配列を巡回し、条件に合わない要素をreturnで飛ばしてから副作用を行う形はよく使われますが、これは押さえたい点です。ログ出力、DOM更新、既存の配列やオブジェクトへの反映では短く収まりますし、これが一つの目安です。

結果を新しく作る処理まで寄せると、mapfilterより意図が弱くなります。forEachは副作用、returnはスキップ、mapは変換、filterは抽出と分けますし、これが一つの目安です。

配列の要素を条件で処理する

偶数だけを集める処理はfilterでも書けますが、既存の配列へ追加しながら別処理も行うならforEachも候補です。奇数で早めにreturnすると、処理本体を浅く保てます。

const numbers = [1, 2, 3, 4, 5, 6, 7, 8];
const evenNumbers = [];

numbers.forEach((num) => {
  if (num % 2 !== 0) {
    return;
  }

  evenNumbers.push(num);
});

console.log(evenNumbers);

結果: 期待される出力は[2, 4, 6, 8]です。奇数の要素ではreturnによりpushへ進まず、偶数だけがevenNumbersへ追加されますが、覚えておくと役立つでしょう。

純粋な抽出だけなら、numbers.filter((num) => num % 2 === 0)のほうが短くなると理解できます。抽出と同時にログや別の状態更新がある場合はforEachも現実的です。

オブジェクトのプロパティを操作する

オブジェクト自体にはforEachがないため、Object.keysObject.valuesObject.entriesで配列化します。キーと値を使うならObject.entriesが読みやすいです。

const profile = {
  name: 'sakura',
  age: 29,
  city: 'tokyo',
  newsletter: true,
};

Object.entries(profile).forEach(([key, value]) => {
  if (typeof value !== 'string') {
    return;
  }

  profile[key] = value.toUpperCase();
});

console.log(profile);

結果: 期待される出力は{ name: 'SAKURA', age: 29, city: 'TOKYO', newsletter: true }です。文字列のプロパティだけがtoUpperCaseで変換されます。

この例は元のprofileを書き換えます。元データを保つならstructuredClone{ ...profile }で複製してから処理すると覚えるとよいでしょう。

フォーム入力の空文字を除外する

入力配列では、空文字や空白だけの値を除外する場面があるのが一般的です。trimで空になった値をreturnすれば、後続の加工を分けられます。

const rawTags = [' JavaScript ', '', 'forEach', '  ', 'return'];
const normalizedTags = [];

rawTags.forEach((tag) => {
  const normalized = tag.trim();

  if (normalized === '') {
    return;
  }

  normalizedTags.push(normalized.toLowerCase());
});

console.log(normalizedTags);

結果: 期待される出力は['javascript', 'foreach', 'return']です。空文字と空白だけの値はスキップされ、残ったタグだけが小文字化されますが、覚えておくと役立つでしょう。

normalizedのような中間変数を置くと、条件判定と追加処理の意味が分かれます。アロー関数を短くしすぎるより、意図が読める名前を優先します。

DOM要素の更新に使う

ブラウザでは、document.querySelectorAllが返すNodeListにもforEachを使える環境があると考えられますし、ここを基本と考えるとよいでしょう。要素ごとのclassList.addtextContent更新は、副作用目的のforEachに合います。

const items = document.querySelectorAll('.menu-item');

items.forEach((item) => {
  if (item.dataset.disabled === 'true') {
    return;
  }

  item.classList.add('is-active');
});

結果: 期待される表示は、data-disabled="true"ではない.menu-itemis-activeクラスが付く状態です。無効化された要素はreturnで処理を飛ばします。

複数要素へイベントを設定する場合はaddEventListenerと組み合わせますし、ここを基本と考えるとよいでしょう。関連する基礎はJavaScriptイベント徹底解説、実装例はJavaScriptにおけるイベントハンドラを完全ガイドが参考になるのが現実的です。

よくあるエラーと対処法

forEach周辺のエラーは、期待した制御フローとのズレから起きやすいです。returnで配列が作られる、非同期処理の完了を待つ、途中終了できる、という誤解が代表例です。

直すときは「戻り値が必要か」「全件処理か途中終了か」「非同期の順番を待つか」を分けます。追いにくい場合は開発者ツールでbreakpointを置き、call stackと変数の変化を確認すると言えるでしょう。

非同期処理で完了前に次へ進む

forEachコールバック関数asyncにしても、外側は各Promiseの完了を待ちません。awaitasync関数内では働きますが、外側の流れは止めません。

const ids = [1, 2, 3];
const results = [];

ids.forEach(async (id) => {
  const value = await fetchValue(id);
  results.push(value);
});

console.log(results);

結果: 期待される出力は、fetchValueの完了前なら[]になる可能性があります。forEachは非同期の完了を待たず、直後のconsole.logへ進むためです。

並列に処理してすべて待つなら、mapPromise配列を作り、Promise.allに渡します。returnしたPromiseが配列に入り、完了後の結果をまとめて受け取れますし、ここがポイントです。

const ids = [1, 2, 3];

async function loadValues() {
  const results = await Promise.all(
    ids.map((id) => {
      return fetchValue(id);
    })
  );

  console.log(results);
}

結果: 期待される出力は、fetchValueが解決した値を並べた配列です。mapreturnPromise配列を作り、Promise.allが完了を待ちますし、ここがポイントです。

forEachの戻り値を配列だと思う

forEachは常にundefinedを返します。アロー関数内でreturn num * 2を書いても、新しい配列は受け取れません。

const numbers = [1, 2, 3];

const doubled = numbers.forEach((num) => {
  return num * 2;
});

console.log(doubled);

結果: 期待される出力はundefinedです。returnした値はforEachの外側へ配列として集まりません。

変換結果が必要ならmapへ置き換えますが、これは押さえたい点です。mapではコールバック関数returnが新しい配列の各要素になります。

const numbers = [1, 2, 3];

const doubled = numbers.map((num) => {
  return num * 2;
});

console.log(doubled);

結果: 期待される出力は[2, 4, 6]です。変換を表す処理ではmapを使うと、戻り値の意味が明確になるのがポイントです。

途中終了したつもりで処理が続く

検索処理でforEachを使うと、一致後も最後までループ処理が続きますが、これは押さえたい点です。最初の一致だけが欲しいならfindのほうが素直です。

const products = [
  { id: 'a', stock: 0 },
  { id: 'b', stock: 3 },
  { id: 'c', stock: 5 },
];

let availableProduct = null;

products.forEach((product) => {
  if (product.stock > 0) {
    availableProduct = product;
    return;
  }
});

console.log(availableProduct);

結果: 期待される出力は{ id: 'c', stock: 5 }です。breturnしても全体は止まらないため、後続のcで値が上書きされます。

findは条件に合う最初の要素を返し、条件が成立した時点で後続の確認を行いません。

const products = [
  { id: 'a', stock: 0 },
  { id: 'b', stock: 3 },
  { id: 'c', stock: 5 },
];

const availableProduct = products.find((product) => {
  return product.stock > 0;
});

console.log(availableProduct);

結果: 期待される出力は{ id: 'b', stock: 3 }です。最初に条件を満たした要素を取得する目的なら、findが読みやすい選択になります。

引数の扱いを誤る

forEachコールバック関数には、要素、インデックス、元配列の順に引数が渡されますし、これが一つの目安です。順番を間違えるとログや条件判定がずれますし、これが一つの目安です。

const tasks = ['write', 'review', 'publish'];

tasks.forEach((task, index, array) => {
  console.log(`${index + 1}/${array.length}: ${task}`);
});

結果: 期待される出力は1/3: write2/3: review3/3: publishです。第1引数が要素、第2引数がインデックス、第3引数が元配列になります。

使わない引数まで書く必要はありません。forEach自身が渡すindexarrayに、毎回デフォルト値を付ける必要も通常ありません。

ℹ️ 補足: 配列メソッドの学習では、forEachの構文だけでなく、戻り値を使うメソッドとの違いを同時に見ると理解が速くなるのが現実的です。新しい配列が欲しい処理にforEachを使っていないか確認しましょう。

forEachを使ったアドバンストテクニック

発展的な使い方では、forEachだけに固執せず、filtermapreduceObject.entriesを組み合わせますが、覚えておくと役立つでしょう。目的ごとに分けると、returnの意味が混ざりません。

外部状態の更新やDOMの直接変更ではforEachが適します。ループ処理の目的を先に決め、アロー関数の短さより可読性を優先します。

複数の配列操作をチェーンする

都市で絞り込み、年齢を取り出し、合計する処理はfiltermapreduceの連結で表せますし、ここを基本と考えるとよいでしょう。各段階の役割が見えやすくなると整理できます。

const members = [
  { name: 'Alice', age: 25, city: 'Tokyo' },
  { name: 'Bob', age: 30, city: 'New York' },
  { name: 'Carol', age: 35, city: 'Tokyo' },
  { name: 'David', age: 28, city: 'London' },
];

const totalAgeInTokyo = members
  .filter((member) => {
    return member.city === 'Tokyo';
  })
  .map((member) => {
    return member.age;
  })
  .reduce((sum, age) => {
    return sum + age;
  }, 0);

console.log(totalAgeInTokyo);

結果: 期待される出力は60です。Tokyoのメンバーだけを残し、年齢を配列化し、最後に合計しています。

forEachで書くと、条件判定と合計更新をひとつの関数に入れる形になります。条件や加工が増えるほど、チェーンで段階を分けたほうが追いやすくなるのが基本です。

条件分岐を関数に切り出す

複雑な条件分岐をforEach内へ直接書くと、ifelse ifreturnpushが混ざりますが、覚えておくと役立つでしょう。判定やメッセージ生成を関数へ分けると、巡回と追加に集中できます。

const users = [
  { name: 'Alice', age: 25, premium: true },
  { name: 'Bob', age: 31, premium: false },
  { name: 'Carol', age: 34, premium: true },
];

function createMessage(user) {
  if (user.premium && user.age >= 30) {
    return `${user.name}: premium campaign`;
  }

  if (!user.premium && user.age >= 30) {
    return `${user.name}: upgrade guide`;
  }

  return `${user.name}: standard news`;
}

const messages = [];

users.forEach((user) => {
  messages.push(createMessage(user));
});

console.log(messages);

結果: 期待される出力は['Alice: standard news', 'Bob: upgrade guide', 'Carol: premium campaign']です。分岐のreturncreateMessageの戻り値として働きます。

forEach内のコールバック関数は短くなり、条件の追加や文言変更はcreateMessageに閉じます。巡回処理とビジネスルールを分けて考えられますし、ここがポイントです。

例外処理で不正データを飛ばす

不正値が混ざる可能性がある場合、trycatchで個別に扱えますし、ここを基本と考えるとよいでしょう。catch内でreturnすれば、その要素だけを止めて他は続けられます。

const values = [10, 20, 'unknown', 30];
const doubled = [];

values.forEach((value) => {
  try {
    if (!Number.isFinite(value)) {
      throw new TypeError('value must be a finite number');
    }

    doubled.push(value * 2);
  } catch (error) {
    console.error(error.message);
    return;
  }
});

console.log(doubled);

結果: 期待される出力は、エラー出力にvalue must be a finite number、通常出力に[20, 40, 60]です。不正な値だけを飛ばして処理を続けます。

入力値の検査だけならfilterで先に絞るほうが単純な場合もあります。通常の条件分岐で済むものはiffilterで表するのが目安です。

Mapに集計する

カテゴリ別件数を数える処理では、MapforEachを組み合わせると明確です。値を返すのではなく、集計用のMapを更新する副作用が目的です。

const orders = [
  { category: 'book' },
  { category: 'tool' },
  { category: 'book' },
  { category: 'food' },
];

const counts = new Map();

orders.forEach((order) => {
  const current = counts.get(order.category) ?? 0;
  counts.set(order.category, current + 1);
});

console.log(Object.fromEntries(counts));

結果: 期待される出力は{ book: 2, tool: 1, food: 1 }です。Mapにカテゴリごとの件数を蓄積し、最後にObject.fromEntriesで確認しやすい形へ変換しています。

同じ集計はreduceでも書けます。集計結果を式として返すならreduce、既存状態を段階的に更新する意図ならforEachが合いるのがポイントです。

⚠️ 注意: forEach内で配列自身を追加・削除すると、読み手が処理順を追いにくくなると言えるでしょう。入力配列と出力先を分ける、またはmapfilterで新しい配列を作る設計を検討してください。

ファイル名や拡張子を配列で扱う処理では、JavaScriptで拡張子を活用する方法のような文字列操作も組み合わせますし、ここがポイントです。日付配列やカレンダー表示では、JavaScriptでカレンダーを作成する例も関連領域になります。

まとめ

JavaScriptforEachreturnを使うと、現在のコールバック関数だけを終了し、次の要素のループ処理へ進みますが、これは押さえたい点です。途中終了したい場面ではforfor...ofsomefindを選びます。

forEachは副作用、mapは変換、filterは抽出、reduceは集約という役割があると理解できます。アロー関数で短く書けても、returnがどこへ渡るかを確認することが大切です。

配列操作の読みやすさはメソッド選びで変わりますし、これが一つの目安です。forEachに変換、抽出、探索、非同期待機をすべて任せず、目的に合う構文へ分けます。

著者: Japanシーモア編集部

Japanシーモアは、Web/IoT/APP/SYS 分野のプログラミング情報を体系的に提供するメディアです。本記事は編集部による執筆とAI支援を組み合わせて制作し、公開前に編集部が校正しています。誤りや改善案がございましたらお問い合わせよりご連絡ください。

※本記事は実在のエンジニア複数名で構成される Japanシーモア編集部が、AI支援を活用して作成・校正・公開しています。

関連記事