メインコンテンツまでスキップ

例外処理 (exception)

JavaScriptにはJavaに似た例外処理の構文があります。例外にはErrorオブジェクトを使い、throw構文で例外を投げます。try-catch構文で例外を捕捉できます。

js
try {
throw new Error("something wrong");
} catch (e) {
// something wrong
console.log(e.message);
}
js
try {
throw new Error("something wrong");
} catch (e) {
// something wrong
console.log(e.message);
}

throw構文

JavaScriptのthrowは例外を投げる構文です。例外として投げるオブジェクトはErrorオブジェクトを使うのが一般的です。

js
throw new Error("network error!");
js
throw new Error("network error!");

JavaScriptのthrowはJavaなどと異なり、何でも投げることができます。プリミティブ型でさえ投げれます。

js
throw "just a string";
js
throw "just a string";

これはアンチパターンです。throwが何でも投げられるとしても、Errorオブジェクトを用いるべきです。Errorオブジェクトを使ったほうがコードの読み手に意外性を与えないからです。加えて、スタックトレースが追えるのはErrorオブジェクトだけだからです。

try-catch構文

JavaScriptで例外を捉えるにはtry-catch構文を使います。例外が投げられる可能性がある部分をtryブロックで囲み、catchブロックで捉えた例外に対する処理を行います。

js
try {
throw new Error("something wrong");
} catch (e) {
console.error(e);
}
js
try {
throw new Error("something wrong");
} catch (e) {
console.error(e);
}

catchの型

TypeScriptではcatchの変数の型はデフォルトでany型になります。

ts
try {
// ...
} catch (e) {
var e: any
}
ts
try {
// ...
} catch (e) {
var e: any
}

型がErrorオブジェクトの型ではなくany型になるのは、JavaScriptの仕様上どんな値がthrowされるか分からないためです。

TypeScriptのコンパイラーオプションのuseUnknownInCatchVariablesを有効にすると、catchの変数の型がunknown型になります。「どんな値がthrowされるか分からない」ことを型として正確に表現できるため、より型安全にしたい場合は、このオプションを有効化するとよいでしょう。

📄️ useUnknownInCatchVariables

例外捕捉catch(e)のeをunknown型として扱う

📄️ undefined型

JavaScriptのundefinedは未定義を表すプリミティブな値です。変数に値がセットされていないとき、戻り値が無い関数、オブジェクトに存在しないプロパティにアクセスしたとき、配列に存在しないインデックスでアクセスしたときなどに現れます。

catchの分岐

JavaやPHPでは捉えるエラーの型に対応するcatchを複数書けますが、JavaScriptとTypeScriptではcatchは1つしか書けません。JavaScriptでエラーの型によってエラーハンドリングを分岐したい場合は、catchブロックの中で分岐を書く方法で対応します。

ts
try {
// ...
} catch (e) {
if (e instanceof TypeError) {
// TypeErrorに対する処理
} else if (e instanceof RangeError) {
// RangeErrorに対する処理
} else if (e instanceof EvalError) {
// EvalErrorに対する処理
} else {
// その他のエラー
}
}
ts
try {
// ...
} catch (e) {
if (e instanceof TypeError) {
// TypeErrorに対する処理
} else if (e instanceof RangeError) {
// RangeErrorに対する処理
} else if (e instanceof EvalError) {
// EvalErrorに対する処理
} else {
// その他のエラー
}
}

try-catchはブロックスコープ

JavaScriptのtry-catch文内の変数はブロックスコープになります。そのため、try-catch内で宣言された変数は、try-catchの外では参照できません。

ts
async function fetchData() {
try {
const res = await fetch("https://jsonplaceholder.typicode.com/todos/1");
const data = await res.json();
console.log(data); // dataが参照できる
} catch (e: unknown) {
return;
}
console.log(data); // dataが参照できない
Cannot find name 'data'.2304Cannot find name 'data'.
}
 
fetchData();
ts
async function fetchData() {
try {
const res = await fetch("https://jsonplaceholder.typicode.com/todos/1");
const data = await res.json();
console.log(data); // dataが参照できる
} catch (e: unknown) {
return;
}
console.log(data); // dataが参照できない
Cannot find name 'data'.2304Cannot find name 'data'.
}
 
fetchData();

📄️ 変数のスコープ

スコープ(scope)とは、変数がどこから参照できるかを定めた変数の有効範囲のことです。JavaScriptには大きく分けてグローバルスコープとローカルスコープの2つがあります。

try-catch文の外でも変数を参照したい場合は、tryの前に代入用の変数をlet宣言しておく必要があります。

ts
async function fetchData() {
let data: any;
try {
const res = await fetch("https://jsonplaceholder.typicode.com/todos/1");
data = await res.json();
} catch (e: unknown) {
return;
}
console.log(data); // dataが参照できる
}
 
fetchData();
ts
async function fetchData() {
let data: any;
try {
const res = await fetch("https://jsonplaceholder.typicode.com/todos/1");
data = await res.json();
} catch (e: unknown) {
return;
}
console.log(data); // dataが参照できる
}
 
fetchData();

finallyブロック

JavaScriptにもJavaやPHPと同じようにfinallyが書けます。finallyは例外が発生しようがしまいが必ず実行される処理です。finallyはtry-catchの後に書きます。finally内の処理はtryとcatchの処理が実行された後に実行されます。

js
try {
// ...
} catch (e) {
// ...
} finally {
// ...
}
js
try {
// ...
} catch (e) {
// ...
} finally {
// ...
}