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 が予期しないレスポンスを受け取った場合はどうなりますか?
  • ユーザーのインターネット接続が失敗した場合はどうなりますか?

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