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 プロパティを使用して、ステータス コードが 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 に渡す方法を示しています。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 が予期しないレスポンスを受信した場合、どうなりますか?
  • ユーザーのインターネット接続に失敗した場合はどうなりますか?

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