目次
エラーと例外のイメージ
「例外が投げられる」とは
「例外が発生する(throwされる)」=
- その時点で 通常の処理フローを中断する
- 一番近い
tryブロックのcatchを探しに行く - 見つからなければ、最終的には 実行環境(ブラウザ・Node)まで伝播し、エラーとして落ちる
という動きです。
function fn() {
throw new Error("何かがおかしい");
console.log("ここには来ない");
}
try {
fn();
console.log("ここにも来ない");
} catch (e) {
console.log("catch:", e.message); // "何かがおかしい"
}
console.log("ここは実行される");Error と組み込みエラー型
基本の Error オブジェクト
const err = new Error("何かエラーが起きました");
console.log(err.name); // "Error"
console.log(err.message); // "何かエラーが起きました"
console.log(err.stack); // スタックトレース(環境依存)name:エラー種別名message:説明文stack:どこから来たか(デバッグ用)
throw するときは、
throw new Error("メッセージ");が基本形です。
よく見る組み込みエラー型
1) TypeError
「型や使い方がおかしい」ときに出るエラー。
null.foo; // TypeError: Cannot read properties of null ...
(123).toUpperCase(); // TypeError自分で投げることもあります:
function double(x) {
if (typeof x !== "number") {
throw new TypeError("x must be number");
}
return x * 2;
}2) ReferenceError
「参照している変数が存在しない」ときに出る。
console.log(foo); // ReferenceError: foo is not defined3) SyntaxError
構文が間違っているとき。
eval("if ("); // SyntaxError- パース時に起きるので、普通のコード内で try/catch することはあまりない
eval/new Functionで動的にコードを評価するときに出会うことがある
4) RangeError
「許容範囲を超えた値」のとき。
new Array(-1); // RangeError
(123).toFixed(100); // RangeError: digits argument must be between ...5) そのほか
URIError:decodeURIComponentなどで不正なエンコードEvalError:歴史的な理由で残っている。実用で見ることはほぼない。AggregateError:複数のエラーをまとめる(Promise.anyなどで使われる)
try / catch / finally の基本
基本構文
try {
// 例外が起きるかもしれない処理
} catch (error) {
// 起きたときの処理
} finally {
// 成功しても失敗しても最後に必ず実行される処理(省略可)
}catch は必須ではなく、try + finally という形も可能です。
try {
// ...
} finally {
// リソース解放など
}シンプルな例
function parseJson(json) {
try {
const data = JSON.parse(json);
console.log("解析成功:", data);
} catch (e) {
console.error("JSON parse error:", e.message);
} finally {
console.log("parseJson 完了");
}
}
parseJson('{"valid": true}');
// 解析成功: { valid: true }
// parseJson 完了
parseJson('{invalid json}');
// JSON parse error: Unexpected token ...
// parseJson 完了try内で throw された例外だけがcatchされるfinallyは「ログ出し」「接続を閉じる」「一時ファイル削除」などに使われる
catch の引数省略
try-catch だけの場合で、エラーオブジェクトを使わないなら、引数を省略できます(比較的新しめの仕様)。
try {
riskyOperation();
} catch {
console.log("何か失敗した");
}throw による例外送出
何でも投げられる(推奨は Error)
JavaScript では、理論上どんな値でも throw できます:
throw "error"; // 文字列
throw 123; // 数値
throw { code: 1 }; // オブジェクトが、実務・読みやすさの観点で非推奨 です。
おすすめは:
- 基本は
Erroror そのサブクラス throw new Error("...");- 種類によって
TypeError,RangeErrorなどを使い分け
function assertNumber(x) {
if (typeof x !== "number") {
throw new TypeError(`Expected number, got ${typeof x}`);
}
}throw の位置と「rethrow」
try {
foo();
} catch (e) {
if (e instanceof TypeError) {
console.log("型エラー:", e.message);
} else {
// 想定外のエラーは再スローして上位に任せる
throw e;
}
}- 「ここで処理できるエラー」と「ここでは処理すべきでないエラー」を分けて扱うために
一部だけハンドリングして、残りはthrowし直す(rethrow) のがよくあるパターンです。
カスタムエラークラス
複雑なアプリやライブラリになると、
- バリデーションエラー
- 認証エラー
- ネットワークエラー
- ドメイン固有のエラー(ゲームロジック、業務ロジックなど)
を区別したくなります。
そのときに使うのが カスタムエラークラス です。
基本形
class ValidationError extends Error {
constructor(message) {
super(message);
this.name = "ValidationError";
}
}
function validateUser(user) {
if (!user.name) {
throw new ValidationError("name は必須です");
}
}
try {
validateUser({}); // name なし
} catch (e) {
if (e instanceof ValidationError) {
console.log("バリデーションエラー:", e.message);
} else {
throw e; // 想定外は再スロー
}
}Errorを継承してnameを自分でセットするのが定番instanceof ValidationErrorで種類ごとに分岐できる
追加情報を持たせる
class HttpError extends Error {
constructor(status, message) {
super(message);
this.name = "HttpError";
this.status = status;
}
}
throw new HttpError(404, "Not Found");statusのようなフィールドを付けておくと、エラーハンドラ側で HTTPレスポンスにそのまま反映できる
同期処理と非同期処理のエラーの違い
ここ、とても重要です。
try-catch は「そのスコープ内の同期処理」だけを捕捉します。
同期ならキャッチできる
try {
JSON.parse("{invalid json}");
} catch (e) {
console.log("同期エラーを捕捉した:", e.message);
}これはOK。
setTimeout 内のエラーは外側の try では捕捉できない
try {
setTimeout(() => {
throw new Error("これは捕捉できない");
}, 0);
} catch (e) {
console.log("ここには来ない");
}理由:
setTimeoutのコールバックは 後で別タイミング で実行される- そのときには、外側の
tryはもう終わっている
setTimeout 内でエラーをキャッチしたければ、その中で try-catch する 必要があります。
setTimeout(() => {
try {
throw new Error("これはこの中で捕まえる");
} catch (e) {
console.log("timeout 内でキャッチ:", e.message);
}
}, 0);Promise のエラーは .catch or async/await + try-catch
非同期のエラーは「Promise の reject」として扱うのが基本です。
// Promiseチェーン
doAsync()
.then(result => {
// ...
})
.catch(err => {
console.error("非同期エラー:", err);
});async/await ではこうなります:
async function main() {
try {
const result = await doAsync();
// ...
} catch (e) {
console.error("async/await で非同期エラー捕捉:", e);
}
}ポイント
try/catchは「そのawaitで拒否された Promise(reject)」を捕捉する- 逆に言うと、Promise を使わない非同期(生のコールバック) は、
tryだけでは救えない
グローバルの未処理エラー
ブラウザには:
window.onerror:キャッチされなかったエラーwindow.onunhandledrejection:キャッチされなかった Promise の reject
Node.js には:
process.on("uncaughtException", ...)process.on("unhandledRejection", ...)
といった「最終手段」もありますが、
基本は個別の箇所でちゃんとハンドリングする方針が大事です。
エラー処理の実務的な考え方
- 「本当に例外にすべきか?」 を考える
- 入力フォームのバリデーション失敗 → 例外ではなく「通常の結果」として返す方がよいことも多い
- プログラミングミス(null参照など) → 例外で落として直すべき
- エラーメッセージは「人間が読んで原因を推測できるレベル」で書く
- 例外を握りつぶさない
- 何もログを出さず
catch (e) {}のように無視すると、デバッグ不可能になる
- 何もログを出さず
- 「ここで回復できるエラー」と「できないエラー」を分けて、できないものは再スロー
コメント