JavaScript を書いていると、存在しないデータを読もうとしたり、壊れた文字列を解析しようとしたりして、エラーでプログラムが止まってしまうことがあります。こうしたエラー(例外)を捕まえて、止まらずに代わりの処理へ進めるための仕組みが try...catch です。この記事では try...catch の基本構文から、エラーの中身を表す Error オブジェクト、後片付けに使う finally、自分でエラーを発生させる throw、そして JSON.parse や fetch での実践的な使い方までを、初心者の方にも分かるように解説します。
目次
そもそも例外(エラー)とは何か
JavaScript では、実行中に処理を続けられない問題が起きると「例外」が発生します。たとえば JSON.parse に壊れた文字列を渡したときや、null のプロパティにアクセスしたときなどです。例外が発生すると、その場でプログラムの実行が中断され、何も対処しなければそれ以降のコードは動かなくなります。
この「中断」を受け止め、エラーが起きても処理を続けたり、ユーザーにメッセージを見せたりできるようにするのが try...catch の役割です。エラーが起こりそうな箇所をあらかじめ囲っておく、というイメージを持つと分かりやすいでしょう。
try…catch の基本構文
基本の形はとてもシンプルです。try ブロックに「エラーが起きるかもしれない処理」を書き、catch ブロックに「エラーが起きたときの処理」を書きます。
try {
// エラーが起きるかもしれない処理
const data = JSON.parse("{ 壊れたデータ }");
console.log(data);
} catch (error) {
// エラーが起きたときだけ実行される
console.log("解析に失敗しました");
}
console.log("ここはエラーが起きても実行される");
try の中でエラーが発生すると、JavaScript はその時点で try ブロックの残りの行を飛ばし、すぐに catch ブロックへ移ります。上の例では console.log(data) は実行されず、代わりに「解析に失敗しました」が表示されます。そして try...catch を抜けた後の最後の行は、エラーの有無にかかわらず実行されます。これがエラーで「止まらない」仕組みです。
catch の後ろの丸かっこに書いた error には、発生したエラーの情報を持つオブジェクトが渡されます。名前は error でなくても e や err でも構いません。エラーの中身が不要であれば、この引数は省略することもできます。
catch で受け取る Error オブジェクト
catch が受け取るのは、多くの場合 Error オブジェクトです。ここにはエラーの種類や内容を表す情報が入っており、よく使うのは次の2つのプロパティです。
| プロパティ | 内容 |
|---|---|
name | エラーの種類を表す名前(例: SyntaxError、TypeError) |
message | エラーの内容を説明する文字列 |
try {
const user = null;
console.log(user.name); // null のプロパティを読もうとして失敗
} catch (error) {
console.log(error.name); // "TypeError"
console.log(error.message); // "Cannot read properties of null..."
}
error.name でエラーの種類を判別できるため、「SyntaxError のときはこう、それ以外はこう」といった分岐も書けます。ログに error.message を出しておくと、後から原因を調べるときの手がかりになります。
finally で必ず実行する後片付けを書く
try...catch には、finally ブロックを続けて書くことができます。finally は、エラーが起きても起きなくても必ず最後に実行されるブロックです。読み込み中の表示を消す、開いたリソースを閉じるといった「成功・失敗どちらでも必要な後片付け」を書くのに向いています。
showLoading(); // 読み込み中の表示を出す
try {
const data = JSON.parse(input);
render(data);
} catch (error) {
showMessage("データの読み込みに失敗しました");
} finally {
hideLoading(); // 成功でも失敗でも、必ず読み込み表示を消す
}
もし finally を使わずに後片付けを書くと、try 側と catch 側の両方に同じ hideLoading() を書く必要があり、片方を書き忘れる原因になります。finally にまとめておけば、その心配がありません。
throw で自分からエラーを発生させる
エラーは JavaScript が自動で発生させるだけでなく、throw を使って自分で意図的に発生させることもできます。「この条件を満たさないなら処理を中断したい」というときに使うと、後続の処理を確実に止められます。投げる値は文字列でも構いませんが、name や message を備えた Error オブジェクトを投げるのが定番です。
function divide(a, b) {
if (b === 0) {
// Error オブジェクトを作って投げる
throw new Error("0 で割ることはできません");
}
return a / b;
}
try {
console.log(divide(10, 0));
} catch (error) {
console.log(error.message); // "0 で割ることはできません"
}
throw が実行されると、その関数の処理はそこで中断され、呼び出し元の catch までジャンプします。入力値のチェックなどで「ありえない値が来たら例外にする」という設計にしておくと、不正なデータがそのまま処理を通り抜けてしまう事故を防げます。
実践例: API レスポンスを安全に処理する
try...catch がもっとも役立つのは、外部から受け取るデータを扱う場面です。fetch で取得したデータの解析を try...catch で囲っておけば、通信エラーや壊れた JSON が返ってきても、アプリ全体が止まらずに済みます。async/await と組み合わせる場合も、同期処理と同じように try...catch で囲うだけです。
async function getUser(id) {
try {
const res = await fetch(`/api/users/${id}`);
// fetch は通信できれば 404 でもエラーにならないので自分で確認する
if (!res.ok) {
throw new Error(`サーバーエラー: ${res.status}`);
}
const user = await res.json();
return user;
} catch (error) {
console.log("取得に失敗:", error.message);
return null; // 失敗時は null を返して呼び出し側で対応
}
}
ここでのポイントは、fetch は通信そのものに失敗したときだけ例外を投げ、サーバーが 404 や 500 を返した場合は例外にならないことです。そのため res.ok を確認し、問題があれば throw で自分から例外を発生させて catch に集約しています。こうすると、通信エラーもステータスエラーも一箇所でまとめて扱えます。
非同期処理でエラーが捕まえられないとき
try...catch がうまく働かない代表的なケースが、setTimeout などのコールバック内で起きるエラーです。try を抜けた後に時間差で実行されるコールバックの例外は、すでに処理を抜けてしまった catch では受け止められません。
try {
setTimeout(() => {
throw new Error("時間差で発生");
}, 1000);
} catch (error) {
// ここには来ない。try は一瞬で抜けてしまっている
console.log("捕まえられない");
}
この場合は、エラーが起きうる処理(コールバックの中)そのものを try...catch で囲む必要があります。Promise を使うなら、await を try で囲うか、.catch() メソッドをつなげて受け止めます。「try...catch は、その瞬間に実行される処理にしか効かない」と覚えておくと、こうした取りこぼしを防げます。
まとめ
try...catch は、エラーでプログラムを止めずに安全に処理を続けるための基本の仕組みです。要点を振り返ります。
tryにエラーが起きうる処理、catchにエラー時の処理を書くcatchが受け取るErrorオブジェクトのname・messageで原因を判別できるfinallyは成功・失敗どちらでも必ず実行され、後片付けに使うthrow new Error(...)で自分から例外を発生させ、処理を中断できるfetchはステータスエラーでは例外を投げないので、res.okを確認してthrowするsetTimeoutなど時間差で動くコールバックのエラーは、外側のtry...catchでは捕まえられない
まずは JSON.parse や fetch のような「外部のデータを扱う処理」を try...catch で囲うことから始めてみてください。それだけで、予期しない入力でアプリが丸ごと止まってしまう事故をぐっと減らせます。