Fetch API をラップした fetchWithErrorHandling
を作る
fetch APIを使うにあたって、ネットワークエラーなどのハンドリングを考えると少しエラーハンドリングの順序を工夫しないといけない。結論から言うと、以下のようなコードにすることで例えばChromeの ERR_CONNECTION_REFUSED
も含めて拾うことができる。
const handleErrors = (res) => {
if (res.ok) {
return res;
}
switch (res.status) {
case 400: throw Error('INVALID_TOKEN');
case 401: throw Error('UNAUTHORIZED');
case 500: throw Error('INTERNAL_SERVER_ERROR');
case 502: throw Error('BAD_GATEWAY');
case 404: throw Error('NOT_FOUND');
default: throw Error('UNHANDLED_ERROR');
}
};
const fetchWithErrorHandling = (url, options) =>
fetch(url, options)
// 1. ネットワーク周りなどのリクエスト以前の段階でのエラーを処理する
.catch((e) => { throw Error(e); })
// 2. サーバサイドで発行されたエラーステータスを処理する
.then(handleErrors)
// 3. 以上2つをパスした正常なレスポンスからJSONオブジェクトをパースする
.then(res => res.json());
重要なポイントなのは handleErrors
でAPI側のエラーを拾う前に Promise.prototype.catch
でネットワークエラーなどの、APIへのリクエスト自体の失敗を拾っておくこと。これが必ず最初にないと、そもそもレスポンスが返されず handleErrors
の内部で res
を見る処理そのものがエラーになってしまう。
fetchWithErrorHandling
を使ってみる
以下は async/await と一緒に fetchWithErrorHandling
を使う例。
const unauthorizedRequest = async () => {
try {
await fetchWithErrorHandling('https://httpstat.us/401');
} catch (e) {
console.log(e); // Error: UNAUTHORIZED
}
};
unauthorizedRequest();
余談: エラーステータスを例外としてcatchすべきか否か
そもそも論として、APIからのレスポンスとしての404や401を try...catch の中でエラーとしてハンドリングするべきなのか? という点には、議論の余地がある。なぜなら、40x系や50x系のエラーレスポンスをサーバサイドから受け取れているという点で Fetch API は処理を完了しており、アプリケーションとしては正常であるから。
fetchWithErrorHandling
は実装上の利便性のためにフロントエンド側のエラーとサーバサイド側のエラーをひとつにまとめて例外として処理しているが、厳密に例外処理を区分する場合には今回の実装は必ずしも適切ではない。