この記事では、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
を使用して処理中のリクエストをキャンセルしています。処理中のリクエストとは、開始されたものの、完了していないネットワーク リクエストのことです。
処理中のリクエストをキャンセルする必要があるシナリオはさまざまですが、最終的にはユースケースと環境によって異なります。次のコードは、AbortSignal
を Fetch API に渡す方法を示しています。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 が予期しないレスポンスを受け取った場合はどうなりますか?
- ユーザーのインターネット接続が失敗した場合はどうなりますか?
ウェブページの複雑さに応じて、さまざまなシナリオで機能とユーザー インターフェースを説明するフローチャートを作成することもできます。