JavaScript:エラー処理(try / catch / throw)
1学習の目的
- try / catch でエラーを受け止め、1件の不正データで全体が止まらないコードを書けるようになる。
- throw で自分からエラーを出し、検証と対応を分離できるようになる。JavaScript特有の「エラーにならず静かに壊れる」現象も見抜けるようになる。
2基礎解説
| やりたいこと | 書き方 | 意味 |
|---|---|---|
| エラーを受け止める | try { } catch (e) { } | 止まらずに続行できる |
| 内容を読む | e.name / e.message | 種類とメッセージ |
| 自分で出す | throw new Error("理由") | 不正を呼び出し元に知らせる |
| 必ず実行する | finally { } | 成功・失敗どちらでも |
- JavaScriptは多くの異常でエラーを出さない。10 / 0 は Infinity、Number("abc") は NaN、存在しないキーは undefined。止まらず静かに壊れるのが最大の怖さ。
- だから自分で検証して throw する必要がある。if (Number.isNaN(v)) throw new Error("数値ではありません"); のように書く。
- よく出るエラー:TypeError(null や undefined のプロパティを読んだ)、ReferenceError(未定義の変数)、SyntaxError(JSON.parse の失敗)。
- catch (e) の e はエラーオブジェクト。e.message で理由、e instanceof TypeError で種類を判定できる。
- finally は成功でも失敗でも必ず実行される。後始末を書く場所で、return があっても実行される。
現場使用例:APIリクエストの失敗、JSONの解析失敗、フォーム入力の検証、CSVの不正行スキップ。外部から来るデータを扱う処理には必ず必要。
// JavaScriptはエラーにならないことが多い
console.log(10 / 0); // Infinity ← 止まらない
console.log(Number("abc")); // NaN ← 止まらない
// だから自分で検証して throw する
const parseAmount = (text) => {
const value = Number(text);
if (Number.isNaN(value)) {
throw new Error("数値に変換できません");
}
return value;
};
try {
console.log(parseAmount("100")); // 100
console.log(parseAmount("abc")); // ここで throw される
} catch (e) {
console.log(`エラー:${e.message}`);
} finally {
console.log("処理を終了しました");
}
3基本ドリル(10問)
出力 == エラー:テストエラー
catch (e) { } の中で e.message を使う。
try {
throw new Error("テストエラー");
} catch (e) {
console.log(`エラー:${e.message}`);
}
try/catchの最小形。e.message には throw のときに渡した文字列が入る。
A. エラーで停止する B. Infinity と表示される C. 0 と表示される D. NaN と表示される 選択★☆☆無料
解答 == B
JavaScriptはゼロ除算をエラーにしない。
B
Pythonなら ZeroDivisionError で止まるが、JavaScriptは Infinity を返して進む。この「止まらなさ」が原因不明のバグを生む。
出力 == TypeError
try の中で null.foo を書き、catch (e) で e.name を出す。
try {
null.foo;
} catch (e) {
console.log(e.name);
}
TypeError は最頻出のエラー。「Cannot read properties of null」というメッセージを見たら、null や undefined のものにアクセスしていると考える。
try {
throw new Error("失敗");
} ____ (e) {
console.log(e.message);
}
出力 == 失敗
「捕まえる」という意味の英単語。
catch
catch は投げられたエラーを受け止める。ここが無いとプログラム全体が停止する。
A. エラーが起きたときだけ B. エラーが起きなかったときだけ C. どちらの場合も必ず D. 実行されない 選択★☆☆無料
解答 == C
名前のとおり「最後に必ず」。
C
成功・失敗にかかわらず実行される。try の中に return があっても finally は実行されるので、後始末の置き場所として確実。
出力 == 数値に変換できません
Number("abc") は NaN になる。Number.isNaN(値) で判定する。
const value = Number("abc");
if (Number.isNaN(value)) {
console.log("数値に変換できません");
}
NaN は == でも === でも自分自身と一致しないという特殊な値。だから value === NaN では判定できず、Number.isNaN() を使う。
const check = (n) => {
if (n < 0) {
____ new Error("マイナスは不可");
}
return n;
};
try {
check(-1);
} catch (e) {
console.log(e.message);
}
出力 == マイナスは不可
「投げる」という意味の英単語。
throw
throw はその場で関数を抜け、呼び出し元の catch まで飛ぶ。return と違い、途中の処理はすべて中断される。
A. エラーになる B. 0 C. NaN D. null 選択★★☆無料
解答 == C
「数値ではない」を表す特別な値。
C
NaN は計算に混ざるとすべてを NaN にする。合計が NaN になったら、どこかで変換に失敗していると疑う。
出力 == 10 / 変換エラー / 25(3行)
ループの中に try/catch を入れる。NaN なら throw して catch で受け止める。
const values = ["10", "abc", "25"];
for (const v of values) {
try {
const n = Number(v);
if (Number.isNaN(n)) {
throw new Error("変換エラー");
}
console.log(n);
} catch (e) {
console.log(e.message);
}
}
try/catch をループの中に置くのが要点。外に置くと最初の失敗でループごと終わってしまう。1件ずつ守るのが正しい形。
出力 == 解析失敗:SyntaxError
catch (e) の e.name を使う。
try {
JSON.parse("{壊れたJSON}");
} catch (e) {
console.log(`解析失敗:${e.name}`);
}
JSON.parse は数少ない「ちゃんとエラーを出す」処理。APIの応答が壊れていることは実際にあるので、必ず try で囲む。
4実践シナリオ(5問)
出力3行が完全一致
JavaScriptは 10/0 でエラーにならず Infinity を返すので、自分で b === 0 を判定する必要がある。
const safeDiv = (a, b) => {
if (b === 0) {
return null;
}
return a / b;
};
for (const [a, b] of [[10, 2], [10, 0], [7, 3]]) {
const result = safeDiv(a, b);
if (result === null) {
console.log(`${a} ÷ ${b} = 計算できません`);
} else {
console.log(`${a} ÷ ${b} = ${result.toFixed(2)}`);
}
}
JavaScriptは0除算でエラーを出さないので、try/catch では捕まえられない。自分で条件を書いて防ぐしかないのがPythonとの大きな違い。
「変換成功:[120,85,43]」「成功3件 / 失敗2件」「合計:248」
※空文字は Number("") が 0 になるため、空文字も失敗として扱うこと コーディング★★☆無料
出力3行が完全一致
空文字のチェックを先に入れる。!v で空文字を弾いてから Number.isNaN を見る。配列の表示は JSON.stringify()。
const raw = ["120", "abc", "85", "", "43"];
const nums = [];
let failed = 0;
for (const v of raw) {
try {
if (!v) {
throw new Error("空です");
}
const n = Number(v);
if (Number.isNaN(n)) {
throw new Error("数値ではありません");
}
nums.push(n);
} catch (e) {
failed++;
}
}
console.log(`変換成功:${JSON.stringify(nums)}`);
console.log(`成功${nums.length}件 / 失敗${failed}件`);
console.log(`合計:${nums.reduce((s, n) => s + n, 0)}`);
Number("") が 0 になるのがJavaScriptの罠。空欄が0として集計に混ざると、平均値が静かに狂う。NaN チェックだけでは不十分で、空文字も明示的に弾く必要がある。
'{"name":"田中"}' / "{壊れた}" / '{"name":"鈴木"}' コーディング★★☆無料
出力3行が完全一致
entries() で番号を振りながら回し、try の中で JSON.parse する。catch では e.name を出す。
const inputs = ['{"name":"田中"}', "{壊れた}", '{"name":"鈴木"}'];
for (const [i, text] of inputs.entries()) {
try {
const data = JSON.parse(text);
console.log(`[${i + 1}] 解析成功:${data.name}`);
} catch (e) {
console.log(`[${i + 1}] 解析失敗:${e.name}`);
}
}
APIの応答が必ず正しいJSONとは限らない。サーバーがエラーページのHTMLを返すこともある。JSON.parse は必ず try で囲むのが鉄則。
在庫5の「ノート」に3個・5個の順に出荷を試み、「出荷しました:3個」「[StockError] 在庫が足りません(残り2個)」の2行を出力せよ。 コーディング★★★無料
出力2行が完全一致
class StockError extends Error { constructor(m) { super(m); this.name = "StockError"; } } と書く。在庫は let で管理して減らしていく。
class StockError extends Error {
constructor(message) {
super(message);
this.name = "StockError";
}
}
let stock = 5;
const ship = (qty) => {
if (qty > stock) {
throw new StockError(`在庫が足りません(残り${stock}個)`);
}
stock -= qty;
return qty;
};
for (const qty of [3, 5]) {
try {
console.log(`出荷しました:${ship(qty)}個`);
} catch (e) {
console.log(`[${e.name}] ${e.message}`);
}
}
自作エラークラスを作ると、呼び出し側が「在庫の問題だけ」を選んで捕まえられる。e instanceof StockError で判定でき、他のバグと混同しなくなる。
「[10] 処理開始」→ 成功なら「→ 結果:100」(10倍)/失敗なら「→ 変換に失敗しました」→「[10] 処理終了」の順に出力し、最後に「完了:2件処理」と出力すること。 コーディング★★★無料
出力7行が完全一致
ループの中で開始を出力 → try/catch → finally で終了を出力。finally はエラーの有無に関係なく実行される。
const data = ["10", "abc"];
for (const d of data) {
console.log(`[${d}] 処理開始`);
try {
const n = Number(d);
if (Number.isNaN(n)) {
throw new Error("変換失敗");
}
console.log(`→ 結果:${n * 10}`);
} catch (e) {
console.log("→ 変換に失敗しました");
} finally {
console.log(`[${d}] 処理終了`);
}
}
console.log(`完了:${data.length}件処理`);
finally があるので失敗した回でも必ず「処理終了」が記録される。実務ではここでファイルを閉じたり、ローディング表示を消したりする。書き忘れると画面がぐるぐる回り続ける原因になる。
5仕上げ課題
const rows = [
{ name: "田中", age: "30", email: "tanaka@example.com" },
{ name: "鈴木", age: "abc", email: "suzuki@example.com" },
{ name: "", age: "25", email: "sato@example.com" },
{ name: "高橋", age: "45", email: "takahashi" },
{ name: "伊藤", age: "38", email: "ito@example.com" },
];
検証ルール(ValidationError という自作エラークラスで throw すること):
① 名前が空 → 「名前が未入力です」
② 年齢が数値に変換できない → 「年齢が不正です」
③ メールに @ が含まれない → 「メール形式が不正です」
出力仕様(9行):
=== 入力検証 ===
[1] OK 田中(30歳)
[2] NG 年齢が不正です
[3] NG 名前が未入力です
[4] NG メール形式が不正です
[5] OK 伊藤(38歳)
---
有効:2件 / エラー:3件
平均年齢:34.0歳
出力9行が完全一致
class ValidationError extends Error を定義する。entries() で番号を振りながら回し、try の中で3つの検証を順に行う。メールの @ 判定は email.includes("@")。有効な行は配列に貯めて、最後に reduce で平均を出す(toFixed(1))。
class ValidationError extends Error {
constructor(message) {
super(message);
this.name = "ValidationError";
}
}
const rows = [
{ name: "田中", age: "30", email: "tanaka@example.com" },
{ name: "鈴木", age: "abc", email: "suzuki@example.com" },
{ name: "", age: "25", email: "sato@example.com" },
{ name: "高橋", age: "45", email: "takahashi" },
{ name: "伊藤", age: "38", email: "ito@example.com" },
];
console.log("=== 入力検証 ===");
const valid = [];
let errors = 0;
for (const [i, row] of rows.entries()) {
try {
if (!row.name) {
throw new ValidationError("名前が未入力です");
}
const age = Number(row.age);
if (Number.isNaN(age)) {
throw new ValidationError("年齢が不正です");
}
if (!row.email.includes("@")) {
throw new ValidationError("メール形式が不正です");
}
valid.push({ name: row.name, age });
console.log(`[${i + 1}] OK ${row.name}(${age}歳)`);
} catch (e) {
console.log(`[${i + 1}] NG ${e.message}`);
errors++;
}
}
const averageAge = valid.reduce((sum, v) => sum + v.age, 0) / valid.length;
console.log("---");
console.log(`有効:${valid.length}件 / エラー:${errors}件`);
console.log(`平均年齢:${averageAge.toFixed(1)}歳`);
検証は throw、対応は catch、失敗しても次の行へ——これが実務のデータ取り込み処理の完成形。5,000行のCSVに1行だけ壊れた行が混ざっていても、残り4,999行を処理し切れる。
この章で最も重要なのは、JavaScriptは放っておいてもエラーを出してくれないという事実だ。Number("abc") は NaN、10/0 は Infinity、Number("") は 0——どれも止まらずに進み、集計結果だけが静かに狂う。「エラーを捕まえる」以上に「自分でエラーを作り出す」ことが重要で、それが throw の役割になる。
次章からはブラウザの世界に入る。DOM操作を学ぶと、ここまで console.log に出していた結果を、実際の画面に表示できるようになる。