Fetch API 使用時のエラー処理を実装する

この記事では、Fetch API を使用する際のエラー処理方法について説明します。Fetch API を使用すると、リモート ネットワーク リソースにリクエストを送信できます。リモート ネットワーク呼び出しを行うと、ウェブページでさまざまなネットワーク エラーが発生する可能性があります。

以降のセクションでは、発生する可能性のあるエラーと、エラーや予期しないネットワーク状態に耐えうる適切なレベルの機能を提供するコードを作成する方法について説明します。復元力のあるコードにより、ユーザーの満足度を維持し、ウェブサイトの標準レベルのサービスを提供できます。

このセクションでは、ユーザーが "My Travels.mp4" という名前の新しい動画を作成し、動画共有ウェブサイトに動画をアップロードしようとするシナリオについて説明します。

Fetch を使用する場合は、ユーザーが動画を正常にアップロードするハッピー パスを簡単に検討できます。ただし、ウェブ デベロッパーは、それほどスムーズではない他のパスについても計画する必要があります。このような(望ましくない)パスは、ユーザー エラー、予期しない環境条件、動画共有ウェブサイトのバグが原因で発生することがあります。

ユーザー エラーの例

  • ユーザーが動画ファイルではなく画像ファイル(JPEG など)をアップロードしている。
  • ユーザーが間違った動画ファイルをアップロードし始めます。アップロードの途中で、ユーザーがアップロードする正しい動画ファイルを指定します。
  • 動画のアップロード中に、ユーザーが誤って [アップロードをキャンセル] をクリックした。

環境変化の例

  • 動画のアップロード中にインターネット接続がオフラインになる。
  • 動画のアップロード中にブラウザが再起動します。
  • 動画のアップロード中に動画共有ウェブサイトのサーバーが再起動される。

動画共有ウェブサイトのエラーの例

  • 動画共有ウェブサイトでは、スペースを含むファイル名を処理できません。"My Travels.mp4" の代わりに、"My_Travels.mp4""MyTravels.mp4" などの名前が想定されます。
  • 動画共有ウェブサイトでは、許容される最大ファイルサイズを超える動画をアップロードできません。
  • 動画共有ウェブサイトが、アップロードした動画の動画コーデックをサポートしていない。

こうした事例は、実際に発生しています。このような例は過去に遭遇したことがあるかもしれません。前述のカテゴリから 1 つずつ例を取り上げ、次の点を説明します。

  • 動画共有サービスが特定の例を処理できない場合、デフォルトの動作は次のどれですか。
  • この例で、ユーザーはどのような結果を期待しているでしょうか。
  • プロセスを改善するにはどうすればよいですか?
アクション ユーザーが間違った動画ファイルをアップロードし始めます。アップロードの途中で、ユーザーがアップロードする正しい動画ファイルを指定します。
デフォルトの動作 元のファイルはバックグラウンドでアップロードを続け、新しいファイルも同時にアップロードされます。
ユーザーの期待 ユーザーは、追加のインターネット帯域幅を浪費しないように、元のアップロードが停止することを期待しています。
改善できる点 新しいファイルのアップロードが開始される前に、JavaScript が元のファイルの取得リクエストをキャンセルします。
アクション 動画のアップロード中にインターネット接続が切断された。
デフォルトの動作 アップロードの進行状況バーが 50% で止まっているようです。最終的に、Fetch API でタイムアウトが発生し、アップロードされたデータが破棄されます。インターネット接続が復旧したら、ユーザーはファイルを再アップロードする必要があります。
ユーザーの期待 ユーザーは、ファイルがアップロードできない場合は通知されることを期待しています。また、オンラインに戻ると、アップロードが 50% の時点で自動的に再開されることを期待しています。
改善できる点 アップロード ページには、インターネット接続の問題がユーザーに通知され、インターネット接続が再開されるとアップロードが再開されることがユーザーに安心してもらえるように表示されます。
アクション 動画共有ウェブサイトでは、スペースを含むファイル名を処理できません。「My Travels.mp4」ではなく、「My_Travels.mp4」や「MyTravels.mp4」などの名前が想定されます。
デフォルトの動作 アップロードが完全に完了するまで待つ必要があります。ファイルがアップロードされ、進行状況バーに「100%」と表示されたら、進行状況バーに「もう一度お試しください」というメッセージが表示されます。
ユーザーの期待 ユーザーは、アップロードの開始前、または少なくともアップロードの最初の 1 秒以内にファイル名の制限について通知されることを想定しています。
改善できる点 動画共有サービスがスペースを含むファイル名をサポートしている場合は、別の方法として、アップロードの開始前にファイル名の制限事項をユーザーに通知することもできます。または、動画共有サービスが詳細なエラー メッセージとともにアップロードを拒否します。

Fetch API でエラーを処理する

以下のコード例では、コードを簡素化できるため、最上位の awaitブラウザ サポート)を使用しています。

Fetch API がエラーをスローする

この例では、try/catch ブロック ステートメントを使用して、try ブロック内でスローされたエラーをキャッチします。たとえば、Fetch API が指定されたリソースを取得できない場合、エラーがスローされます。このような catch ブロック内では、有意義なユーザー エクスペリエンスを提供するようにしてください。スピナー(進行状況を示す一般的なユーザー インターフェース)がユーザーに表示される場合は、catch ブロック内で次のアクションを実行できます。

  1. ページからスピナーを削除します。
  2. 問題の原因とユーザーが利用できるオプションを説明する有益なメッセージを提供します。
  3. 利用可能なオプションに基づいて、[再試行] ボタンをユーザーに表示します。
  4. バックグラウンドで、エラーの詳細をエラー トラッキング サービスまたはバックエンドに送信します。このアクションによりエラーがログに記録され、後で診断できるようになります。
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 プロパティを使用して、ステータス コードが 200299 の範囲内にあるかどうかを確認します。
  • 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 を渡す方法を示しています。AbortSignalAbortController に接続されており、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 が予期しないレスポンスを受信した場合、どうなりますか?
  • ユーザーのインターネット接続が失敗した場合はどうなりますか?

ウェブページの複雑さに応じて、さまざまなシナリオの機能とユーザー インターフェースを記述するフローチャートをスケッチすることもできます。