この記事では、Fetch API を使用する際のエラー処理方法について説明します。Fetch API を使用すると、リモート ネットワーク リソースにリクエストを送信できます。リモート ネットワーク呼び出しを行うと、ウェブページでさまざまなネットワーク エラーが発生する可能性があります。
以降のセクションでは、発生する可能性のあるエラーと、エラーや予期しないネットワーク状態に耐えうる適切なレベルの機能を提供するコードを作成する方法について説明します。復元力のあるコードにより、ユーザーの満足度を維持し、ウェブサイトの標準レベルのサービスを提供できます。
ネットワーク エラーの発生を予測する
このセクションでは、ユーザーが "My Travels.mp4"
という名前の新しい動画を作成し、動画共有ウェブサイトに動画をアップロードしようとするシナリオについて説明します。
Fetch を使用する場合は、ユーザーが動画を正常にアップロードするハッピー パスを簡単に検討できます。ただし、ウェブ デベロッパーは、それほどスムーズではない他のパスについても計画する必要があります。このような(望ましくない)パスは、ユーザー エラー、予期しない環境条件、動画共有ウェブサイトのバグが原因で発生することがあります。
ユーザー エラーの例
- ユーザーが動画ファイルではなく画像ファイル(JPEG など)をアップロードしている。
- ユーザーが間違った動画ファイルをアップロードし始めます。アップロードの途中で、ユーザーがアップロードする正しい動画ファイルを指定します。
- 動画のアップロード中に、ユーザーが誤って [アップロードをキャンセル] をクリックした。
環境変化の例
- 動画のアップロード中にインターネット接続がオフラインになる。
- 動画のアップロード中にブラウザが再起動します。
- 動画のアップロード中に動画共有ウェブサイトのサーバーが再起動される。
動画共有ウェブサイトのエラーの例
- 動画共有ウェブサイトでは、スペースを含むファイル名を処理できません。
"My Travels.mp4"
の代わりに、"My_Travels.mp4"
や"MyTravels.mp4"
などの名前が想定されます。 - 動画共有ウェブサイトでは、許容される最大ファイルサイズを超える動画をアップロードできません。
- 動画共有ウェブサイトが、アップロードした動画の動画コーデックをサポートしていない。
こうした事例は、実際に発生しています。このような例は過去に遭遇したことがあるかもしれません。前述のカテゴリから 1 つずつ例を取り上げ、次の点を説明します。
- 動画共有サービスが特定の例を処理できない場合、デフォルトの動作は次のどれですか。
- この例で、ユーザーはどのような結果を期待しているでしょうか。
- プロセスを改善するにはどうすればよいですか?
Fetch API でエラーを処理する
以下のコード例では、コードを簡素化できるため、最上位の await
(ブラウザ サポート)を使用しています。
Fetch API がエラーをスローする
この例では、try
/catch
ブロック ステートメントを使用して、try
ブロック内でスローされたエラーをキャッチします。たとえば、Fetch API が指定されたリソースを取得できない場合、エラーがスローされます。このような catch
ブロック内では、有意義なユーザー エクスペリエンスを提供するようにしてください。スピナー(進行状況を示す一般的なユーザー インターフェース)がユーザーに表示される場合は、catch
ブロック内で次のアクションを実行できます。
- ページからスピナーを削除します。
- 問題の原因とユーザーが利用できるオプションを説明する有益なメッセージを提供します。
- 利用可能なオプションに基づいて、[再試行] ボタンをユーザーに表示します。
- バックグラウンドで、エラーの詳細をエラー トラッキング サービスまたはバックエンドに送信します。このアクションによりエラーがログに記録され、後で診断できるようになります。
try {
const response = await fetch('https://website');
} catch (error) {
// TypeError: Failed to fetch
console.log('There was an error', error);
}
後でログに記録したエラーを診断する際に、テストケースを作成して、ユーザーが問題に気付く前にこのようなエラーを検出できます。エラーに応じて、テストは単体テスト、統合テスト、承認テストのいずれかになります。
ネットワーク ステータス コードがエラーを表している場合
このコードサンプルでは、常に HTTP ステータス コード 429 Too Many Requests
で応答する HTTP テストサービスにリクエストを送信します。興味深いことに、レスポンスは catch
ブロックに到達しません。404 ステータスは、他の特定のステータス コードと同様に、ネットワーク エラーを返すのではなく、正常に解決します。
HTTP ステータス コードが成功したことを確認するには、次のいずれかのオプションを使用します。
Response.ok
プロパティを使用して、ステータス コードが200
~299
の範囲内にあるかどうかを確認します。Response.status
プロパティを使用して、レスポンスが成功したかどうかを確認します。Response.headers
などの他のメタデータを使用して、レスポンスが成功したかどうかを評価します。
let response;
try {
response = await fetch('https://httpbin.org/status/429');
} catch (error) {
console.log('There was an error', error);
}
// Uses the 'optional chaining' operator
if (response?.ok) {
console.log('Use the response here!');
} else {
console.log(`HTTP Response Code: ${response?.status}`)
}
組織やチーム内の人と協力して、考えられる HTTP レスポンス ステータス コードを把握することをおすすめします。バックエンド デベロッパー、デベロッパー オペレーション、サービス エンジニアは、想定していないエッジケースに関する独自の分析情報を提供できる場合があります。
ネットワーク レスポンスの解析中にエラーが発生した場合
このコード例は、レスポンスの本文の解析で発生する可能性のある別のタイプのエラーを示しています。Response
インターフェースには、テキストや JSON など、さまざまな種類のデータを解析するための便利なメソッドが用意されています。次のコードでは、HTTP テスト サービスに対してネットワーク リクエストが送信され、レスポンス本文として HTML 文字列が返されます。ただし、レスポンスの本文を JSON として解析しようとすると、エラーがスローされます。
let json;
try {
const response = await fetch('https://httpbin.org/html');
json = await response.json();
} catch (error) {
if (error instanceof SyntaxError) {
// Unexpected token < in JSON
console.log('There was a SyntaxError', error);
} else {
console.log('There was an error', error);
}
}
if (json) {
console.log('Use the JSON here!', json);
}
さまざまなレスポンス形式を受け取るようにコードを準備し、予期しないレスポンスがユーザーのウェブページを破損しないようにする必要があります。
次のようなシナリオについて考えてみましょう。有効な JSON レスポンスを返すリモート リソースがあり、Response.json()
メソッドで正常に解析されています。サービスが停止することがあります。停止すると、500 Internal Server Error
が返されます。JSON の解析中に適切なエラー処理手法が使用されていない場合、ハンドルされていないエラーがスローされるため、ユーザーのページが破損する可能性があります。
ネットワーク リクエストが完了する前にキャンセルする必要がある場合
このコードサンプルでは、AbortController
を使用して、処理中のリクエストをキャンセルします。処理中のリクエストは、開始されたが完了していないネットワーク リクエストです。
処理中のリクエストをキャンセルする必要があるシナリオはさまざまですが、最終的にはユースケースと環境によって異なります。次のコードは、Fetch API に AbortSignal
を渡す方法を示しています。AbortSignal
は AbortController
に接続されており、AbortController
には abort()
メソッドが含まれています。このメソッドは、ネットワーク リクエストをキャンセルする必要があることをブラウザに通知します。
const controller = new AbortController();
const signal = controller.signal;
// Cancel the fetch request in 500ms
setTimeout(() => controller.abort(), 500);
try {
const url = 'https://httpbin.org/delay/1';
const response = await fetch(url, { signal });
console.log(response);
} catch (error) {
// DOMException: The user aborted a request.
console.log('Error: ', error)
}
まとめ
エラー処理の重要な要素の一つは、問題が発生する可能性のあるさまざまな部分を定義することです。シナリオごとに、ユーザーに適切なフォールバックが用意されていることを確認します。取得リクエストについては、次のような質問を自問します。
- ターゲット サーバーが停止した場合はどうなりますか?
- Fetch が予期しないレスポンスを受信した場合、どうなりますか?
- ユーザーのインターネット接続が失敗した場合はどうなりますか?
ウェブページの複雑さに応じて、さまざまなシナリオの機能とユーザー インターフェースを記述するフローチャートをスケッチすることもできます。