TypeScriptでAPIエラーレスポンスに型をつけて処理する方法!AxiosとFetchの例外ハンドリング徹底解説
生徒
「TypeScriptで外部のデータを取得するときに、エラーが発生した場合の型定義や処理の方法がよくわかりません。安全にエラーをキャッチする方法はありますか?」
先生
「TypeScriptでは、通信エラーのときに戻ってくるデータにもしっかりと型をつけることができます。AxiosやFetchといった通信ツールごとに、安全なエラー処理の方法が存在します。」
生徒
「具体的なプログラムの書き方や、型を判別するテクニックを知りたいです!」
先生
「それでは、初心者の方にもわかりやすいように、基礎知識から具体的な実装手順までを順番に見ていきましょう!」
1. API通信とエラーレスポンスとは?
インターネットを経由して、別のシステムやサーバーからデータを受け取ったり送ったりすることをAPI通信と呼びます。APIはアプリケーション・プログラミング・インターフェースの略称で、システム同士をつなぐ窓口のような役割を持っています。パソコンを触ったことがない方に向けて例えるなら、お店で注文をして料理を受け取るような仕組みです。正常に注文が通れば美味しい料理が届きますが、売り切れやメニューにないものを頼むと「それはありません」というエラーメッセージが返ってきます。この、失敗したときに返ってくる返事のことをエラーレスポンスと言います。
プログラムの世界では、通信が常に成功するとは限りません。インターネットの接続が途切れてしまったり、指定したURLが間違っていたり、サーバー側で予期せぬトラブルが発生したりすることがあります。そうした不測の事態が起きたときに、プログラムが突然終了して画面が真っ白になってしまうのを防ぐために、エラー情報を正しく受け止めて画面に「通信に失敗しました」と親切に表示する仕組みが必要になります。これがエラー処理の基本です。
2. なぜエラーレスポンスに型をつける必要があるのか
TypeScriptを使う最大のメリットは、データに型(データの種類や構造のルール)を指定して、プログラムのミスを未然に防ぐことができる点にあります。しかし、外部のサーバーから返ってくるエラー内容は、プログラムを実行する前の段階ではどのような形をしているのか確定していません。そのため、初期状態では何でも受け入れる特別な型として処理されてしまうことが多いのです。
もし型をつけずにエラーの中身を操作しようとすると、存在しない項目を読み込もうとして、エラー処理の最中にさらに別のエラーが発生するという最悪の事態になりかねません。エラーレスポンスに型をしっかりと定義しておくことで、エラーメッセージが格納されている場所を正確に把握できるようになります。これにより、開発者は予測可能性の高い、安全で頑丈なプログラムを組み立てることができるようになります。
3. エラーレスポンスの型を設計する手順
まずは、サーバーからどのようなエラー情報が返ってくるのか、その形をあらかじめ定義します。これを型定義と呼びます。一般的なシステムでは、エラーが発生した理由を説明するメッセージと、エラーを識別するための番号が含まれていることが多いです。
今回は、エラーのメッセージとステータスコードを保持するシンプルな型オブジェクトを作成してみましょう。プログラムの記述は以下のようになります。
type MyApiError = {
message: string;
statusCode: number;
};
このプログラムについて解説します。typeというのは、新しい型のルールを作成するためのキーワードです。ここではMyApiErrorという名前の型を作っています。その中身として、文字を意味するstring型のmessageと、数値を意味するnumber型のstatusCodeを定義しています。これで、エラーデータの設計図が完成しました。
4. Fetchを使った基本的なエラーハンドリング
ウェブブラウザに標準で備わっている通信機能であるFetchを使って、実際にデータを取得しながらエラーを処理する方法を学びましょう。Fetchは非常にシンプルで便利な機能ですが、通信がつながった場合には、サーバー側でエラーが発生していても成功として扱ってしまう性質があります。そのため、通信が成功したかどうかを判定する条件分岐を自分で書く必要があります。
プログラムの動きを確認するための具体的なコードは以下の通りです。
async function loadUserData() {
try {
const response = await fetch("https://example.com/api/user");
if (!response.ok) {
throw new Error("サーバーでエラーが発生しました");
}
const data = await response.json();
console.log(data);
} catch (error) {
if (error instanceof Error) {
console.log(error.message);
}
}
}
実行結果は以下のようになります。
サーバーでエラーが発生しました
ここで新しい言葉がたくさん出てきました。asyncとawaitは、時間の時間がかかる通信処理を、上から下へ順番にすっきりと書くための仕組みです。tryのあとの波括弧の中に正常に動かしたい処理を書き、もしその中で問題が起きると、即座にcatchのあとの波括弧の中へと処理が飛びます。response.okは、通信が成功したときは真になり、失敗したときは偽になります。頭に感嘆符がついている!response.okは、成否が反転して「失敗したとき」という意味になります。throw new Errorは、自分で意図的にエラーを発生させてcatchブロックへ移動させるための命令です。instanceofは、エラーの具体的な種類を確認するための便利な道具です。
5. Axiosを用いた高度なエラー判定と型付け
実際の開発では、Fetchよりもさらに便利で高機能なAxiosという外部ライブラリ(便利な機能の詰め合わせ)がよく使われます。Axiosは、サーバーからエラーが返ってきた場合に、自動的にそれを検知してcatchブロックへ処理を移してくれるため、プログラムの記述を簡潔に保つことができます。
さらに、Axiosには専用のエラー判定機能が最初から備わっています。これを利用して、エラーレスポンスに型を適用する具体的なプログラムを見てみましょう。
import axios, { AxiosError } from "axios";
type ApiFieldError = {
error: string;
code: number;
};
async function fetchProductData() {
try {
await axios.get("https://example.com/api/product");
} catch (error) {
if (axios.isAxiosError(error)) {
const axiosError = error as AxiosError<ApiFieldError>;
if (axiosError.response) {
console.log(axiosError.response.data.error);
console.log(axiosError.response.data.code);
}
} else {
console.log("予期せぬエラーが発生しました");
}
}
}
実行結果は以下のようになります。
商品が見つかりません
404
プログラムの解説をします。最初にimportを使って、Axiosの機能と、エラーの型であるAxiosErrorを取り込んでいます。axios.isAxiosError(error)という命令を使うことで、発生したエラーが本当にAxiosの通信に関連するものかどうかを厳密に調べることができます。その中にあるasというキーワードは、型アサーションと呼ばれるもので、「このデータは指定した型であるとみなしてください」とシステムに指示を出す役割を持っています。これにより、事前に定義したApiFieldErrorの形式として安全にエラーの内容を読み出すことが可能になります。axiosError.responseが存在するかどうかを確認することで、サーバーから確実に返事があった場合のみ処理を行うという二重の安全対策を施しています。
6. 型ガード関数を活用した安全なデータ検証
TypeScriptには、データが特定の型を満たしているかどうかを自分で判定するための関数を作る機能があります。これを型ガード関数と呼びます。これを使うと、どのようなデータが飛んでくるかわからない不確実な状況でも、プログラムが安全に動くことを保証できるようになります。初心者の方には少し難しく感じるかもしれませんが、パズルの型枠にパーツがカチッとはまるかどうかをテストする関数だとイメージしてください。
実際に型ガード関数を自作して、通信エラーのデータを検証するプログラムを書いてみましょう。
type CustomErrorResponse = {
errorMessage: string;
};
function isCustomError(obj: any): obj is CustomErrorResponse {
return obj !== null && typeof obj === "object" && "errorMessage" in obj;
}
function handleError(errorData: any) {
if (isCustomError(errorData)) {
console.log(errorData.errorMessage);
} else {
console.log("不明なエラー形式です");
}
}
実行結果は以下のようになります。
認証に失敗しました
プログラムの仕組みを説明します。関数の引数の部分にあるobj: anyのany型は、何でも入る魔法の型です。そして、戻り値の型として指定されているobj is CustomErrorResponseという記述が型ガードの核心です。これは「この関数が真を返したならば、引数として渡されたオブジェクトは確実に型の条件を満たしています」という宣言になります。関数の中では、データが空っぽ(null)ではないこと、データの種類が物体(object)であること、そしてその中にerrorMessageという項目がしっかりと含まれていること、という条件を満たしているかを調べています。この関数を条件分岐のif文に挟むことで、安全にメッセージを取り出すことができるようになります。