[JavaScript/TypeScript]リストに含まれる/含まれないをチェックする(some, every, includes)

はじめに

JavaScriptやTypeScriptでは、配列内に特定の値が含まれているかどうかを判定する処理は非常に頻繁に登場します。フォーム入力の検証、権限リストのチェック、タグのフィルタリングなど、あらゆる場面で「要素が含まれているか/含まれていないか」を判断することが求められます。

本記事では、そのような含有判定をシンプルかつ明確に実装するための基本的な関数として、includes、some、every の3つを取り上げます。これらを適切に使い分けることで、条件式の可読性を高め、意図を正確に表現することが可能になります。

また、実務でよく発生する判定パターンを体系的に整理し、それぞれに最適な実装方法を紹介します。配列操作の理解を深め、堅牢で読みやすいコードを書くための一助となれば幸いです。

本記事内のサンプルコードはすべてJavaScriptで記述しています。TypeScriptを利用する場合も、基本的な構文や挙動は同様です。

含有判定の基本

配列に特定の要素が含まれているかどうかを判断するためには、まずJavaScript/TypeScriptにおける配列操作の基本的な考え方を理解しておく必要があります。含有判定は単純な値比較にとどまらず、条件式の設計や意図の明確化にも関わるため、正しい関数選択が重要です。

この章では、まず配列操作の基本概念を確認したうえで、含有判定において特に使用頻度の高い3つの関数 — includessomeevery — の特徴と使い分けについて整理します。

JavaScript/TypeScriptにおける配列操作の基本概念

JavaScriptおよびTypeScriptでは、配列(Array)は順序付きのデータ集合を扱うための基本的なデータ構造です。配列は数値や文字列といったプリミティブ値だけでなく、オブジェクトや関数など任意の型の要素を格納できます。

配列は0から始まるインデックスでアクセスされ、length プロパティによって要素数を取得できます。たとえば次のように操作します。

const fruits = ["apple", "banana", "orange"];
console.log(fruits[0]); // "apple"
console.log(fruits.length); // 3

配列を操作する方法は多岐にわたりますが、大きく分けて次の2つの目的で使い分けられます。

  1. 要素を取得・加工する
    例:mapfilterreducefind など
    → 配列の内容を変換したり、特定の要素を抽出したりする際に利用します
  2. 条件を判定する
    例:includessomeevery など
    → 配列が特定の条件を満たすかどうかを真偽値(true/false)で返す場合に利用します

特に本記事のテーマである「含有判定」は、後者の条件判定系メソッドに分類されます。これらのメソッドは、ループ処理を自動的に内部で実行するため、従来の for 文や forEach に比べてコードを短く、かつ意図を明確に記述できる点が大きな利点です。

次節では、含有判定で中心的に利用される3つの関数 — includessomeevery — の特徴と基本的な使い方を解説します。

判定処理でよく使われる3つの関数(includes/some/every)

配列内の要素が特定の条件を満たすかどうかを確認する際、JavaScript/TypeScriptでは主に includessomeevery の3つのメソッドが利用されます。これらはいずれも真偽値を返す判定メソッドであり、コードの意図を簡潔かつ明確に表現できる点が特徴です。

以下では、それぞれの概要と基本的な違いを整理します。

includes:値の存在を直接判定する

includes は、配列内に特定の値が含まれているかどうかを単純に確認するためのメソッドです。プリミティブ型の比較に適しており、=== による厳密等価で比較を行います。

const fruits = ["apple", "banana", "orange"];
fruits.includes("banana"); // true
fruits.includes("grape");  // false

オブジェクトや関数など参照型を比較する場合は、同一インスタンスでなければ一致しません。そのため、条件付きの判定が必要な場合は someevery を用います。

some:条件を満たす要素が「1つでも」あるか判定する

some は、コールバック関数を用いて任意の条件を満たす要素が1つでも存在するかを確認します。条件式を柔軟に指定できるため、オブジェクト配列などに対しても有効です。

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

users.some(u => u.active); // true

1つでも条件を満たせば true を返し、全ての要素が不一致の場合のみ false となります。

every:全ての要素が条件を満たすか判定する

every は、配列内のすべての要素が条件を満たしているかを確認します。

1つでも条件を満たさない要素があると、false が返されます。

const scores = [80, 90, 100];
scores.every(s => s >= 70); // true
scores.every(s => s >= 90); // false

some と対を成す関係であり、「一部が一致する」か「すべてが一致する」かの違いで使い分けます。

まとめ

メソッド判定対象条件指定戻り値主な用途
includes値の一致不可(固定比較)真偽値単純な値比較
some任意の条件可能真偽値条件を満たす要素があるか
every任意の条件可能真偽値すべての要素が条件を満たすか

これら3つの関数を正しく使い分けることで、配列内の含有判定を明確かつ意図通りに記述できます。

次章では、これらを用いて実務で頻出する判定パターンを体系的に整理します。

判定パターンの整理

配列の含有判定は、一見すると単純な処理に見えますが、実際の業務ロジックでは「どの要素が」「どのように」含まれているかを厳密に区別する必要があります。たとえば「特定の1要素が存在する」ケースと「候補群のうちいずれかが含まれる」ケースでは、意図する判定条件が異なり、使用すべきメソッドの選択も変わります。

本章では、まず「チェック対象の数」と「含み方」の観点から判定の分類を整理し、その上で実務上よく登場する5つの基本パターンを体系的にまとめます。これにより、要件に応じた関数選択と条件設計を一貫して行えるようになります。

チェック対象数と含み方の分類

配列の含有判定を整理する際は、まず「チェック対象の数」「含み方(条件の満たし方)」の2軸で分類すると明確になります。この2つを組み合わせることで、すべての含有パターンを体系的に整理できます。

チェック対象の数

判定したい対象が単一か複数かによって、使用するメソッドやロジックの構造が変わります。

種類説明典型的な用途使用例
単一要素チェック対象が1つだけの場合IDやタグの存在確認などarr.includes(value)
複数要素候補群のいずれか/すべてを確認する場合権限リスト、フィルタ条件、カテゴリー選択などtargets.some(...) / targets.every(...)

単一要素であれば includes で十分ですが、複数要素を扱う場合は someevery と組み合わせることで柔軟な条件判定が可能になります。

含み方(条件の満たし方)

対象が配列内に「どのように」含まれているかという観点です。

主に次の4種類に分類できます。

種類意味対応メソッド構成
いずれかを含む候補のうち1つでも含まれていればよいsome + includes
すべてを含むすべての候補が含まれている必要があるevery + includes
いずれかを含まない少なくとも1つは含まれないsome + !includes
すべてを含まないどれも含まれていないevery + !includes

これらを「チェック対象の数」と組み合わせることで、実務で必要な含有判定ロジックをほぼ網羅できます。

次節では、この分類をもとに、現場で頻出する5つの代表的なパターンを整理します。

実務で頻出する5つの組み合わせ

前節で整理した「チェック対象数」と「含み方」を組み合わせると、理論上はさまざまなパターンが考えられます。しかし、実務において頻繁に登場するのは次の5つの組み合わせにほぼ限定されます。

いずれも日常的な開発シーンで登場する明確な意図を持ったパターンです。

ある要素を含む

単一の値が配列に存在するかを判定する、最も基本的なケースです。フォーム入力の検証、ユーザーIDの存在確認など、頻出する単純チェックに使用します。

const fruits = ["apple", "banana", "orange"];

// 単純な値の存在確認
console.log(fruits.includes("banana")); // true
console.log(fruits.includes("grape"));  // false

includesは要素をループして探し、引数の要素が存在しているかを===で比較して真偽値で返します。比較は===で行われるため、型が異なると一致しない点に注意が必要です。

いずれかの要素を含む

複数候補のうち、少なくとも1つでも含まれていればよいケースです。特定の権限を1つでも持っていれば許可、特定のタグを1つでも持っていれば対象、といった条件に用います。

const fruits = ["apple", "banana", "orange"];
const targets = ["grape", "banana", "melon"];

// 候補のうち1つでも含まれていれば true
const hasAny = targets.some(t => fruits.includes(t));

console.log(hasAny); // true

someは「1つでも条件を満たす要素があるか」を返すメソッドで、includesと組み合わせることで「候補のいずれかが存在するか」を簡潔に表現できます。

すべての要素を含む

複数候補すべてが配列内に含まれている必要があるケースです。要求条件をすべて満たしているか、特定の属性群をすべて保持しているかを確認する際に利用されます。

const fruits = ["apple", "banana", "orange"];
const required = ["banana", "orange"];

// 候補すべてが含まれていれば true
const hasAll = required.every(r => fruits.includes(r));

console.log(hasAll); // true

everyは「すべての要素が条件を満たすか」を返すメソッドで、includesと組み合わせることで「完全に含まれるか」を簡潔に表現できます。候補のうち、1つでも含まれない要素がある場合はfalseを返します。

いずれかの要素を含まない

候補群の中で少なくとも1つが配列に含まれていない場合に検出したいケースです。設定漏れや未登録要素の検知など、例外的状況の判定に使われます。

const fruits = ["apple", "banana", "orange"];
const required = ["banana", "melon"];

// 候補のうち1つでも含まれていなければ true
const missingAny = required.some(r => !fruits.includes(r));

console.log(missingAny); // true

someは前述のとおり「1つでも条件を満たす要素があるか」を返すメソッドであるため、!includes(含まれていない)と組み合わせることで「どれかが欠けているか」を確認することができます。このことから「完全一致ではない」や「要素が不足している」ことをチェックするのに有効です。

すべての要素を含まない

候補群のいずれも配列に存在しないことを確認するケースです。除外条件やフィルタ処理、禁止リストなどに多く用いられます。

const fruits = ["apple", "banana", "orange"];
const excluded = ["grape", "melon"];

// 候補のすべてが含まれていなければ true
const hasNone = excluded.every(e => !fruits.includes(e));

console.log(hasNone); // true

everyは前述のとおり「すべての要素が条件を満たすか」を返すメソッドであるため、!includes(含まれていない)と組み合わせることで「全く含まれない」を確認することができます。このことから1つも入っていないことをチェックする除外リストやブラックリストの判定などに有効です。


これら5パターンを体系的に理解しておくことで、条件式を意図通りに設計できるだけでなく、コードレビュー時にもロジックの誤読を防ぐことができます。

おわりに

配列の含有判定は、日常的な開発で頻出する基本的な処理の一つですが、要件の解釈次第で適切な実装方法は大きく変わります。includessomeevery の3つのメソッドを正しく使い分けることで、冗長なループ処理を避け、コードの意図を明確に表現することが可能になります。特に本記事で整理した5つのパターンを理解しておくと、配列に対する条件判定を一貫性をもって設計でき、後続の保守やレビューでも誤解を防ぐことができます。

小さなユーティリティの選択が、最終的にはシステム全体の可読性と信頼性につながる点を意識するとよいでしょう。今後さらに複雑なデータ構造を扱う場面でも、ここで紹介した原則は有効です。

まずは「何を、どのように含むのか」を明確にし、意図に最も適したメソッドを選ぶことが重要です。

[JavaScript/TypeScript] isNaN よりも Number.isNaN を使おう

JavaScript で数値を扱う際に、意外と頻繁に登場するのが NaN (Not-a-Number) という特殊な値です。NaN は「数値型ではあるが有効な数値ではない」ことを表しており、計算処理の途中で不正な演算が行われたときに発生します。たとえば、文字列を数値に変換できなかった場合や、0 / 0 のような数学的に定義できない演算を行った場合に NaN が返されます。

この NaN の存在は非常に厄介です。なぜなら、NaN は どんな値とも等しくない(NaN === NaN ですら false になる) という性質を持っているため、通常の比較演算子で判定できないからです。そのため、JavaScript には isNaN という専用の判定関数が用意されています。

しかし、この isNaN は一見便利に見える反面、型変換を暗黙に行うために意図しない結果を返すことがある という落とし穴があります。特に実務では、ユーザー入力や API レスポンスといった外部データを扱う際に「これは本当に計算に使える数値なのか?」を確実に判定したい場面が多く、この挙動がバグや不具合の温床になりかねません。

そこで登場するのが Number.isNaN です。Number.isNaNisNaN の欠点を解消し、厳密に「値が NaN であるか」を判定します。本記事では isNaNNumber.isNaN の挙動の違いを具体例とともに解説し、さらに実務で役立つ「数値妥当性チェック関数」の実装例、そして TypeScript を用いた型安全なアプローチまで紹介します。

isNaN の挙動

isNaN はグローバル関数であり、引数を一度数値へと強制的に変換してから、その結果が NaN かどうかを判定する という仕組みになっています。

この「暗黙の型変換」が最大の特徴であり、同時に混乱の原因でもあります。

たとえば次のように文字列 "foo" を渡した場合、JavaScript はまずこれを数値に変換しようと試みます。しかし "foo" は数値に変換できないため NaN となり、結果として isNaN("foo")true を返します。

逆に "123" のように数値として解釈可能な文字列は、内部的に 123 に変換されます。そのため isNaN("123")false となり、一見すると「有効な数値である」と判断されてしまいます。

さらに undefined を渡した場合も注意が必要です。undefined は数値に変換されると NaN になるため、isNaN(undefined)true となります。これも直感的ではなく、型安全性の観点からすると危険な挙動です。

console.log(isNaN("foo"));      // true  ("foo" → NaN)
console.log(isNaN("123"));      // false ("123" → 123)
console.log(isNaN(undefined));  // true  (undefined → NaN)
console.log(isNaN(NaN));        // true

つまり isNaN「数値に変換できないものを検出する」関数 とも言えます。

一方で「そもそも入力が数値型かどうか」を判定したい場合には役立ちません。"123" のような文字列を「有効な数値」とみなして通してしまうため、実務では想定外のデータが後続処理に紛れ込むリスクが高くなります。

このため isNaN を無条件に利用すると、入力チェックやバリデーションにおいて「思っていたのと違う」挙動になりやすく、バグの原因となりやすいのです。

Number.isNaN の挙動

Number.isNaN は ES6 で追加されたメソッドで、isNaN の問題点を解消するために導入されました。最大の特徴は 引数の型変換を一切行わない ことです。つまり、渡された値が「厳密に NaN であるかどうか」だけを判定します。

この挙動により、前述の isNaN で見られたような「文字列や undefined が暗黙に数値変換されてしまう」という予期せぬ動きは発生しません。

console.log(Number.isNaN("foo"));      // false
console.log(Number.isNaN("123"));      // false
console.log(Number.isNaN(undefined));  // false
console.log(Number.isNaN(NaN));        // true

たとえば "foo" を渡しても NaN 判定は行われず、単に「文字列なので NaN ではない」と判断されます。"123" も同様で、「数値に変換できるかどうか」は考慮せず「これは数値型ではない」として false を返します。また undefined の場合も型変換は行われないため、結果は false となります。

一方で、本当に NaN が渡された場合だけは確実に true が返ってきます。これにより 「値が NaN そのものかどうか」 を判定するための純粋で信頼できる手段を得られるわけです。

この挙動は、前述の isNaN と比べると非常に直感的でわかりやすいです。isNaN が「数値に変換できるかどうか」という観点で判定していたのに対し、Number.isNaN は「数値型の中で NaN という特別な値であるかどうか」だけに絞り込んでいるためです。

このため後述の比較章でも触れるように、バリデーションや入力チェックで意図しない値を通してしまうリスクを避けるためには、Number.isNaN を使うのが圧倒的に安全 という結論につながります。

違いのまとめ

ここまで見てきたように、isNaNNumber.isNaN は一見似ているものの、挙動は大きく異なります。表で整理すると次のようになります。

入力値isNaNNumber.isNaN
“foo”truefalse
“123”falsefalse
undefinedtruefalse
NaNtruetrue
123falsefalse

“foo” のケース

isNaN("foo")"foo" を数値に変換しようとし、結果的に NaN となるため true を返します。これは「数値に変換できない」という観点から見れば正しいですが、文字列がそのまま通ってしまうのは実務的には危険です。

一方 Number.isNaN("foo") は変換を行わないため、単なる文字列として扱われ、false が返ります。こちらの方が「これは数値ではない」という意図に近い結果です。

“123” のケース

"123" のように数値に変換可能な文字列については、isNaNNumber.isNaN もどちらも false を返します。つまり 表面上の判定結果は同じ です。

ただし、その理由は異なります。

  • isNaN("123") の場合は、内部で "123" を数値に変換し、結果が 123 になるため「NaN ではない」と判断しています。
  • Number.isNaN("123") の場合は、そもそも "123" は数値型ではないため「NaN と一致しない」という理由で false を返します。

このように両者は同じ結果を返しますが、「変換したうえで判定しているのか」「そのままの型で判定しているのか」 というロジックの違いが存在します。

undefined のケース

isNaN は引数を数値に変換してから判定を行うため、undefinednull に対しても意外な結果を返します。

  • undefined は数値変換されると NaN になるため、isNaN(undefined)true を返します。
  • null は数値変換されると 0 になるため、isNaN(null)false を返します。

一方で Number.isNaN は型変換を行わないため、両方とも「NaN そのものではない」と判断し、false を返します。

console.log(isNaN(undefined));     // true   (undefined → NaN)
console.log(Number.isNaN(undefined)); // false

console.log(isNaN(null));          // false  (null → 0)
console.log(Number.isNaN(null));   // false

このように、isNaNundefinedNaN と誤認し、逆に null を数値 0 として扱ってしまうため直感的ではありません。対して Number.isNaN はどちらの値も単なる「NaN ではない別の型」として処理するため、より安全で一貫性のある挙動を示します。

NaN と数値そのもののケース

最後に、実際に NaN そのものや通常の数値を渡した場合の挙動です。ここについては isNaNNumber.isNaN の両者ともに同じ結果を返します。

console.log(isNaN(NaN));         // true
console.log(Number.isNaN(NaN));  // true

console.log(isNaN(123));         // false
console.log(Number.isNaN(123));  // false
  • NaN を渡した場合は、どちらも正しく true を返します。
  • 有効な数値(例: 123)を渡した場合は、どちらも false を返します。

一見すると「両者に差はない」と思えますが、この挙動は NaN が非常に特殊な値であること を改めて示しています。

通常の比較演算子では NaN を検出できません。例えば NaN === NaNfalse になります。これは ECMAScript の仕様で、NaN は「どんな値とも等しくない」という性質を持っているためです。

console.log(NaN === NaN); // false

そのため、NaN を判定するには専用の関数を使うしかありません。このときに isNaNNumber.isNaN はどちらも正しく動作しますが、問題は「NaN 以外の入力をどう扱うか」です。

前述のとおり isNaN は暗黙の型変換を行うため、undefined"foo" といった値に対しても true を返してしまう可能性があります。つまり「本当に NaN かどうか」を見たい場合には誤判定が起こりやすいのです。

一方 Number.isNaN は「値が数値型かつ NaN である場合のみ true」を返すため、NaN を確実に検出でき、かつ余計な誤判定をしません。

その意味で、NaN そのものを検出するための信頼できる関数は Number.isNaN であると結論づけられます。

Infinity と -Infinity のケース

では、数値型におけるもう一つの特殊な値である Infinity-Infinity を渡した場合はどうでしょうか。

console.log(isNaN(Infinity));        // false
console.log(Number.isNaN(Infinity)); // false

console.log(isNaN(-Infinity));        // false
console.log(Number.isNaN(-Infinity)); // false

どちらの関数も Infinity-Infinity に対しては false を返します。

これは「無限大は特殊な値ではあるものの、NaN ではない」という扱いが仕様として定められているためです。

実際、JavaScript では以下のような挙動をします。

console.log(1 / 0);   // Infinity
console.log(-1 / 0);  // -Infinity
console.log(0 / 0);   // NaN
  • 0 で割った場合でも分子が非ゼロなら Infinity または -Infinity になります。
  • 逆に 0 / 0 のように数学的に定義できない演算を行った場合にのみ NaN が返ります。

したがって、Infinity 系は「有効な数値ではあるが有限ではない」、NaN は「そもそも数値として成立していない」 という違いがあります。

両者ともに isNaN / Number.isNaN での判定結果は一致しますが、意味合いは大きく異なるため、Infinity を検出したい場合には別の関数(例えば Number.isFinite)を使うべきです。


まとめると、

  • isNaN … 引数を数値に変換してから判定するため、外部入力のバリデーションには不向き。
  • Number.isNaN … 値が厳密に NaN であるかどうかだけを判定するため、直感的で安全。

したがって、実務における入力チェックや計算前の検証では、Number.isNaN を優先して用いるべきであることが明確になります。

厳密に「正しい数値」かどうかを判定する関数

ここまでで見たように、isNaNNumber.isNaN は「NaN かどうか」を調べる関数にすぎません。しかし、実務で必要なのは単に NaN を弾くだけではなく、「この値は後続の計算処理に使っても問題ないかどうか」 を判定することです。

例えば次のようなケースを考えてみます。

  • ユーザーがフォームに入力した値をサーバー側で計算に利用する
  • API から取得したレスポンスに数値が含まれており、それを集計に使う
  • センサーやログから取得したデータをグラフに可視化する

これらの場面では、単に NaN を排除するだけでは不十分です。nullundefined、あるいは文字列が紛れ込んでいたとしても、それらを「計算できる値」として扱ってしまうとバグや異常な挙動の原因になります。

そのため、「厳密に数値型である」ことと「NaN ではない」ことを両方確認するチェック関数 が必要になります。

数値型かつ NaN ではない値を判定する関数

まずは「入力が数値型であり、かつ NaN ではないか」をチェックする関数です。

function isUsableNumber(value) {
  return typeof value === "number" && !Number.isNaN(value);
}

// 利用例
console.log(isUsableNumber(123));       // true
console.log(isUsableNumber(NaN));       // false
console.log(isUsableNumber("123"));     // false
console.log(isUsableNumber(null));      // false
console.log(isUsableNumber(undefined)); // false

この関数を使うことで、文字列や null が紛れ込んでも誤って有効な数値と判断されない ため、後続処理を安全に進められます。特に業務システムや数値計算系の処理では必須のガードになります。

文字列も数値として許容したい場合

一方で、ユースケースによっては "123" のような文字列入力を数値に変換して受け入れたい場合もあります。たとえば、フォーム入力やCSVファイルの読み込みなど、外部データは文字列で渡ってくることが多いからです。

その場合は、数値変換したうえで判定を行う関数を用意すると便利です。

function isConvertibleToNumber(value) {
  const num = Number(value);
  return !Number.isNaN(num);
}

// 利用例
console.log(isConvertibleToNumber("123"));   // true  (→ 123 に変換可能)
console.log(isConvertibleToNumber("foo"));   // false (→ NaN)
console.log(isConvertibleToNumber(123));     // true
console.log(isConvertibleToNumber(NaN));     // false

この関数を使えば、外部から文字列として値を受け取った場合でも安全に「数値として利用できるかどうか」を確認できます。

Infinity の扱いに注意

さらに厳密に「正しい数値」を定義したい場合には、Infinity-Infinity も考慮する必要があります。これらは数値型でありながら有限ではないため、計算結果を大きく狂わせる可能性があります。

その場合は Number.isFinite を組み合わせてチェックします。

function isFiniteNumber(value) {
  return typeof value === "number" && Number.isFinite(value);
}

// 利用例
console.log(isFiniteNumber(123));       // true
console.log(isFiniteNumber(NaN));       // false
console.log(isFiniteNumber(Infinity));  // false
console.log(isFiniteNumber(-Infinity)); // false

これで「数値型かつ有限な値」であることを保証でき、最も厳密な意味で「正しい数値」を判定できます。

まとめ

  • Number.isNaN は「NaN かどうか」を判定するだけ。
  • 実務では 数値型であること、場合によっては 有限であること を保証する関数が必要になる。
  • 入力の性質(数値型のみを想定するのか、文字列入力も許容するのか)に応じて、チェック関数を実装し使い分けるのがベストプラクティス。

TypeScript での型安全なアプローチ

JavaScript の場合、isNaNNumber.isNaN のような関数に頼らないと「その値が計算に使える正しい数値かどうか」を判断できません。なぜなら、JavaScript は動的型付け言語であり、関数の引数に文字列や null が渡ってきてもコンパイル時にエラーにならないからです。

そのため、実務で入力チェックを厳密に行いたい場合には、自前で判定関数を作る必要があります。

一方で TypeScript を使えば、コンパイル時に型の安全性を保証できるため、実行時のチェックを最小限に抑えることが可能です。これにより「文字列が紛れ込んでいた」などの典型的なバグを、コードが実行される前に防げます。

数値型だけを受け入れる関数

TypeScript では関数の引数に型を指定できるため、number 型以外の値が渡された時点でコンパイルエラーとなります。

function isUsableNumber(value: number): boolean {
  return !Number.isNaN(value);
}

// 利用例
console.log(isUsableNumber(123)); // true
console.log(isUsableNumber(NaN)); // false

// 以下はすべてコンパイルエラー
// console.log(isUsableNumber("123"));
// console.log(isUsableNumber(null));
// console.log(isUsableNumber(undefined));

JavaScript では実行してみないとエラーが出ないケースも、TypeScript ではコンパイル時に検出できるため、「想定外の型が混入する」リスクを大幅に減らすことができます。

外部入力を扱う場合

ただし、ユーザー入力や API からのレスポンスなど、外部データはすべて unknownstring 型として扱われることが多いため、そのまま数値として信頼することはできません。

この場合は「外部入力を number に変換する処理」と「変換結果の妥当性チェック」を組み合わせるのが現実的です。

function parseNumber(value: unknown): number | null {
  const num = Number(value);
  return Number.isNaN(num) ? null : num;
}

// 利用例
console.log(parseNumber("123")); // 123
console.log(parseNumber("foo")); // null
console.log(parseNumber(42));    // 42

この関数を通すことで、外部入力が不正な場合は null を返すため、安全に扱えます。

つまり 外部データを受け取る段階で型を狭め、アプリケーション内部では常に number 型として扱う、という二段構えが理想的です。

Infinity の扱い

さらに厳密に「正しい数値」を保証したい場合には、Infinity-Infinity を排除することも考えられます。TypeScript ではこれも関数化して型安全に扱えます。

function isFiniteNumber(value: number): boolean {
  return Number.isFinite(value);
}

console.log(isFiniteNumber(123));       // true
console.log(isFiniteNumber(NaN));       // false
console.log(isFiniteNumber(Infinity));  // false
console.log(isFiniteNumber(-Infinity)); // false

このように TypeScript では 引数の型を number に制約した上で、有限数であることを保証する 関数を定義できます。これにより、アプリケーション内部の計算処理に不正な値が入り込むのを防げます。

実務での利点

  • JavaScript 単体 → 「型が曖昧なので実行時チェックが必須」
  • TypeScript 利用時 → 「コンパイル時に型を保証し、実行時チェックは外部入力の変換処理に限定できる」

このように、TypeScript を導入すると「型安全性」と「実行時のバリデーション」をうまく分担でき、堅牢なシステム設計につながります。

おわりに

ここまで isNaNNumber.isNaN の違いを整理し、さらに実務で役立つチェック関数や TypeScript での型安全なアプローチについて解説しました。

まず理解すべきは、isNaN は一見便利な関数ではあるものの、暗黙の型変換を行うため外部入力の妥当性確認には適さないということです。undefined を NaN と判定したり、null0 とみなしたりする挙動は直感に反し、バグの温床になりかねません。

それに対して Number.isNaN は「値が厳密に NaN であるかどうか」だけを判定するため、余計な誤判定がなく、より直感的で安全な結果を返すことができます。単純に「NaN かどうか」を調べたいのであれば、常に Number.isNaN を選ぶべきです。

しかし、実務で本当に必要なのは「NaN 判定」そのものではなく、その値が計算に使える正しい数値であるかどうかの判定です。そのためには typeofNumber.isFinite を組み合わせたチェック関数を定義し、用途に応じて使い分ける必要があります。たとえば「数値型かつ NaN ではないか」「数値に変換可能か」「有限な数値か」など、要件に応じた粒度で判定を行うのが望ましいでしょう。

さらに TypeScript を導入すれば、これらのチェックの多くはコンパイル時に解決できます。型による制約で "123"null が紛れ込むことを防ぎ、実行時のチェックは外部入力の変換に限定する設計が可能です。結果として、JavaScript の弱点を補いながら、より堅牢で信頼性の高いコード を書けるようになります。

結論として、次のように整理できます。

  • NaN 判定には Number.isNaN を使うべき。
  • 入力バリデーションでは、用途に応じて「数値型か」「有限か」などをチェックする専用関数を用意する。
  • TypeScript を使う場合は、型システムで保証できる部分と実行時の変換・チェックを分離し、シンプルな設計を心がける。

これらを徹底することで、「思わぬ型変換により不正な値が計算に紛れ込む」といった典型的なバグを防ぎ、安心して数値を扱えるコード基盤を構築できます。

axiosのエラーかを判別する(axios.isAxiosError)

axiosでREST APIにアクセスするコードを含む複数のコードをtry-catchで囲んでいるときに、axiosのエラーをログなどに出力する際、JSON.stringifyしていたのですが、axios以外が原因でエラーになっている場合、JSON.stringifyでは文字列を得られなかったので対処方法を検討していました。

axios.isAxiosError

最初objectかどうかで判定しようとしていましたがうまく判定できませんでした。axiosではaxiosのエラーかどうかを判別するためのaxios.isAxiosErrorが提供されているため、これを使う方が良さそうです。

async function test() {
  try {
    const response = axios.get("http://httpstat.us/200");
  } catch (error) {
    if (axios.isAxiosError(error)) {
      console.log(JSON.stringify(error, null, 2));
    }
  }

動作確認

この動作を確認するために以下のようなプログラムで確認しました。

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <script src="https://cdn.jsdelivr.net/npm/axios@1.7.8/dist/axios.min.js"></script>
    <script>
      async function test() {
        try {
          const response = await axios.get("http://httpstat.us/200");
          const a = [];
          console.log(a[1].b);
        } catch (error) {
          if (axios.isAxiosError(error)) {
            console.log("Axios Error:", JSON.stringify(error, null, 2));
          } else {
            console.log("Error:", String(error));
          }
        }
      }

      test();
    </script>
  </body>
</html>

このプログラムでは、httpstat.usという指定したHTTPステータスのレスポンスを返してくれるサービスで任意のHTTPステータスを返すようにしています。HTTPステータスが200の場合は例外が発生しないため、後続の処理でundefinedなオブジェクトのプロパティにアクセスしているため例外が発生します。

上記のHTMLにブラウザからアクセスすると、コンソールログに

Error: TypeError: Cannot read properties of undefined (reading 'b')

と表示されます。

一方、

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <script src="https://cdn.jsdelivr.net/npm/axios@1.7.8/dist/axios.min.js"></script>
    <script>
      async function test() {
        try {
          const response = await axios.get("http://httpstat.us/404");
          const a = [];
          console.log(a[1].b);
        } catch (error) {
          if (axios.isAxiosError(error)) {
            console.log("Axios Error:", JSON.stringify(error, null, 2));
          } else {
            console.log("Error:", String(error));
          }
        }
      }

      test();
    </script>
  </body>
</html>

のようにURLの200部分を404に変えてアクセスすると、

Axios Error: {
  "message": "Request failed with status code 404",
  "name": "AxiosError",
  "stack": "AxiosError: Request failed with status code 404\n    at Qe (https://cdn.jsdelivr.net/npm/axios@1.7.8/dist/axios.min.js:1:31804)\n    at XMLHttpRequest.y (https://cdn.jsdelivr.net/npm/axios@1.7.8/dist/axios.min.js:1:36641)\n    at e.<anonymous> (https://cdn.jsdelivr.net/npm/axios@1.7.8/dist/axios.min.js:1:48621)\n    at p (https://cdn.jsdelivr.net/npm/axios@1.7.8/dist/axios.min.js:1:3448)\n    at Generator.<anonymous> (https://cdn.jsdelivr.net/npm/axios@1.7.8/dist/axios.min.js:1:4779)\n    at Generator.throw (https://cdn.jsdelivr.net/npm/axios@1.7.8/dist/axios.min.js:1:3858)\n    at p (https://cdn.jsdelivr.net/npm/axios@1.7.8/dist/axios.min.js:1:9996)\n    at u (https://cdn.jsdelivr.net/npm/axios@1.7.8/dist/axios.min.js:1:10235)",
  "config": {
    "transitional": {
      "silentJSONParsing": true,
      "forcedJSONParsing": true,
      "clarifyTimeoutError": false
    },
    "adapter": [
      "xhr",
      "http",
      "fetch"
    ],
    "transformRequest": [
      null
    ],
    "transformResponse": [
      null
    ],
    "timeout": 0,
    "xsrfCookieName": "XSRF-TOKEN",
    "xsrfHeaderName": "X-XSRF-TOKEN",
    "maxContentLength": -1,
    "maxBodyLength": -1,
    "env": {},
    "headers": {
      "Accept": "application/json, text/plain, */*"
    },
    "method": "get",
    "url": "http://httpstat.us/404"
  },
  "code": "ERR_BAD_REQUEST",
  "status": 404
}

と表示され、axios.isAxiosErroraxiosのエラーかどうかを判別できていることが確認できます。

まとめ

例外が発生したときにエラーページに遷移するという動作をするアプリケーションがあり、コンソールにログを出力しても記録に残らないため、サーバーにログが送るという対処を行っていました。実際に試してみると、axiosのエラーだと、エラーの内容がサーバーのログで確認できましたが、それ以外のエラーの場合は{}になって何も確認できないという事象があり、これを確認できるようにするために、このような検証をしてみました。

try-catchで例外をcatchしていることまでははっきりしていても、axiosのエラーかそれ以外のエラーかを判定する場合は型ではなく、axios.isAxiosErrorで判定するのが確実だということが確認できました。

モバイルバージョンを終了