この記事では、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 が予期しないレスポンスを受信した場合、どうなりますか?
- ユーザーのインターネット接続に失敗した場合はどうなりますか?
ウェブページの複雑さに応じて、さまざまなシナリオの機能とユーザー インターフェースを記述するフローチャートをスケッチすることもできます。