TypeScriptでAPIエラーハンドリング!GraphQLとRESTの例外処理を初心者向けに解説
生徒
「TypeScriptでWebサイトを作っているのですが、APIからデータを受け取るときにエラーが起きると画面が真っ白になってしまいます。どうすればいいですか?」
先生
「それは例外処理、つまりエラーハンドリングができていないからですね。REST APIやGraphQLでは、通信に失敗したりデータが見つからなかったりすることがよくあります。」
生徒
「エラーをあらかじめ予想して、正しく処理する方法があるんですね。具体的にどう書けばいいのでしょうか?」
先生
「それでは、初心者の方でも安心して実装できるエラー処理の基本を一緒に見ていきましょう!」
1. エラーハンドリングと例外処理とは?
プログラミングにおけるエラーハンドリングとは、プログラムの実行中に発生した予期せぬ問題に対して、適切に対応することを指します。これを日本語では「例外処理」とも呼びます。
例えば、あなたがお店に買い物に行ったとしましょう。お目当ての商品が売り切れていた場合、そのまま立ち尽くすのではなく「別の店に行く」や「店員さんに在庫を聞く」といった行動をとりますよね。プログラムも同じです。インターネットを通じてデータを取得する際、サーバーが止まっていたり、URLが間違っていたりすることがあります。そうした「困った事態」が起きたときに、アプリを強制終了させず、「エラーが発生しました」というメッセージを画面に出すなどの対応を決めておくのがエラーハンドリングの役割です。
TypeScriptでは、このエラー処理を非常に厳密に、かつ安全に記述できる仕組みが整っています。特に外部のシステムとやり取りするAPI通信では、エラーは「起きるもの」として考えておくのが鉄則です。
2. try-catch構文の基本を覚えよう
TypeScriptでエラーを捕まえるための最も基本的な道具がtry-catch(トライ・キャッチ)構文です。名前の通り、「まずはやってみて(try)、ダメだったら捕まえる(catch)」という動きをします。
まずtryというブロックの中に、エラーが起きる可能性のあるコードを書きます。もしその中でエラーが発生すると、プログラムの実行は即座に中断され、続くcatchブロックに処理が移ります。これにより、エラーが起きてもプログラム全体が止まってしまうのを防ぐことができます。
パソコンを触ったことがない方でも、これは「守り」の魔法だと考えると分かりやすいでしょう。危険な橋を渡るときに、あらかじめ下にネットを張っておくようなイメージです。
try {
// ここにエラーが起きそうな処理を書く
console.log("通信を開始します...");
throw new Error("通信に失敗しました!"); // 意図的にエラーを発生させる
} catch (error) {
// エラーが起きたときに実行される
console.log("エラーを捕まえましました!内容は以下の通りです。");
console.log(error);
}
通信を開始します...
エラーを捕まえましました!内容は以下の通りです。
Error: 通信に失敗しました!
3. REST APIでのエラー処理の実践
現在、多くのWebサービスで使われているのがREST API(レスト・エーピーアイ)です。これは、特定の住所(URL)にアクセスしてデータをやり取りする仕組みのことです。
REST APIの通信では、fetchという機能を使ってデータを取得するのが一般的です。しかし、fetch自体は通信が成功しても失敗しても、プログラム上は「通信が終わった」とみなされることがあります。例えば、指定したページが存在しない(404エラー)場合でも、通信自体は成立しているため、明示的にチェックが必要です。
以下のコード例では、データの取得に失敗した際に、レスポンスの状態を確認してエラーを投げる方法を解説します。初心者の方は、まず「通信が正常か確認するステップが必要なんだな」と覚えておけば大丈夫です。
async function getUserData() {
try {
const response = await fetch("https://example.com/api/user");
// 通信が成功したか(ステータスコードが200番台か)確認
if (!response.ok) {
// 失敗した場合はエラーを投げる
throw new Error("サーバーからエラーが返されました。");
}
const data = await response.json();
console.log("データを取得しました", data);
} catch (error) {
// ネットワークエラーや上記のthrowで投げたエラーがここに来る
console.error("エラーが発生しました:", error);
}
}
4. GraphQL特有のエラー構造に注意
もう一つの有名な通信形式にGraphQL(グラフ・キューエル)があります。REST APIと似ていますが、大きな違いはエラーの返し方です。
GraphQLの場合、通信自体は成功(200 OK)として返ってきても、その中身に「エラー情報」が含まれていることがよくあります。つまり、箱は届いたけれど中身が壊れていた、という状態です。そのため、単に通信の成否を見るだけでなく、返ってきたデータの中にerrorsという配列が存在するかどうかをチェックしなければなりません。
TypeScriptを使うと、この複雑なデータの形を定義できるため、どの部分でエラーが起きたのかを正確に見極めることができます。初心者の方は、GraphQLでは「データの奥深くまでエラーがないか探しに行く必要がある」と理解しておきましょう。
async function fetchGraphQL(query: string) {
try {
const response = await fetch("https://example.com/graphql", {
method: "POST",
body: JSON.stringify({ query })
});
const result = await response.json();
// GraphQL特有のエラーチェック
if (result.errors) {
console.error("GraphQLでエラーが発生しました");
result.errors.forEach((err: any) => {
console.error("詳細:", err.message);
});
return;
}
console.log("成功データ:", result.data);
} catch (error) {
console.error("通信自体に失敗しました", error);
}
}
5. unknown型と型ガードで安全性を高める
TypeScriptでエラー処理をするときに重要なのが、キャッチしたエラーの型を特定することです。最新のTypeScriptでは、catchで受け取るエラー変数は、デフォルトでunknown型(何かわからない型)として扱われます。
なぜなら、投げられるエラーは文字列かもしれませんし、特別なオブジェクトかもしれません。そのため、そのままではエラーのメッセージを表示させることができません。ここで「型ガード」というテクニックを使い、「もしこのエラーがErrorという型であれば」と確認をしてから処理を進めます。これにより、プログラムが型安全になり、予測不可能な事態を防ぐことができます。
function handleError(error: unknown) {
// 型ガードを使ってエラーの種類を特定する
if (error instanceof Error) {
// ここではerrorがError型だと保証されている
console.log("エラーメッセージ:", error.message);
} else {
// 文字列や数値などが投げられた場合の処理
console.log("未知のエラーが発生しました", String(error));
}
}
エラーメッセージ: 通信に失敗しました!
6. ユーザーに優しいエラーメッセージの表示
プログラミング的なエラー処理ができたら、最後は実際にアプリを使う「人間」のための工夫が必要です。開発者用の難しい英語のメッセージをそのまま画面に出しても、ユーザーは困ってしまいます。
エラーの種類に応じて、「インターネットの接続を確認してください」や「入力内容に誤りがあります」といった、分かりやすい日本語のメッセージを出し分けることが重要です。これを実現するためには、エラーが発生した場所で独自の情報を追加したり、エラーコードを定義したりします。初心者の方は、自分がアプリを使っていて「あ、失敗したんだな」と納得できるメッセージを出すことをゴールにしてみてください。
API通信は目に見えない世界の話ですが、しっかりとしたエラーハンドリングを行うことで、どんな状況でも優しく振る舞う、信頼性の高いシステムを作ることができるようになります。一歩ずつ、確実に覚えていきましょう。