Fetch APIの基本:非同期通信の新しいカタチ

Fetch APIとは何か

JavaScriptの`fetch` APIは、Webブラウザからサーバーへ非同期にリクエストを送信し、そのレスポンスを処理するための、現代的なインターフェースです。従来の`XMLHttpRequest`(XHR)に代わるものとして登場しました。

Fetch APIはPromiseベースで設計されており、非同期処理をよりシンプルかつ強力に記述できるのが大きな特徴です。これにより、コールバック地獄に陥ることなく、コードの可読性とメンテナンス性が向上します。

`fetch()`メソッドはグローバル関数であり、第一引数にリクエスト先のURL、第二引数にリクエストの詳細を設定するオプションオブジェクトを取ります。このシンプルな構造が、Fetch APIの直感的な使いやすさにつながっています。(参考情報より)

Promiseベースの非同期処理

`fetch()`メソッドを実行すると、即座にPromiseが返されます。このPromiseは、サーバーからのレスポンスヘッダーが利用可能になった時点で成功(resolve)するか、ネットワークエラーが発生した場合に失敗(reject)します。

Promiseを扱うことで、`.then()`メソッドで成功時の処理を、`.catch()`メソッドで失敗時の処理をチェーンで記述できます。これにより、非同期処理のフローが明確になり、コードが格段に読みやすくなります。

さらに、ES2017で導入されたasync/await構文を利用すれば、Promiseチェーンをまるで同期処理のように記述することが可能です。これにより、非同期処理のコードがさらに簡潔になり、特に複雑な処理の流れでも理解しやすくなります。

async function fetchData(url) {
  try {
    const response = await fetch(url); // Promiseが解決するまで待機
    // レスポンス処理
  } catch (error) {
    console.error('Fetch error:', error); // ネットワークエラーを捕捉
  }
}

エラーハンドリングの基本

Fetch APIでのエラーハンドリングは、`XMLHttpRequest`とは異なる重要な点があります。`fetch()`が返すPromiseは、ネットワークエラーが発生した場合にのみrejectされます。例えば、CORSエラーやインターネット接続がない場合などです。

これに対し、404 Not Foundや500 Internal Server ErrorといったHTTPエラーレスポンスの場合、Promiseはrejectされません。代わりに、Promiseは解決され、レスポンスオブジェクトが返されます。

そのため、HTTPエラーを検出するには、レスポンスオブジェクトの`ok`プロパティや`status`プロパティを確認する必要があります。`response.ok`はHTTPステータスコードが200-299の範囲内であれば`true`を返し、それ以外は`false`となります。(参考情報より)

async function fetchData(url) {
  try {
    const response = await fetch(url);
    if (!response.ok) { // HTTPエラーをチェック
      throw new Error(`HTTP error! status: ${response.status}`);
    }
    const data = await response.json();
    console.log(data);
  } catch (error) {
    console.error('Fetch error:', error); // ネットワークエラーまたは手動でスローされたエラーを捕捉
  }
}

Fetch APIでGETリクエストを送信する方法

基本的なGETリクエスト

Fetch APIを使ってGETリクエストを送信するのは非常に簡単です。`fetch()`メソッドの第一引数に、リクエスト先のURLを指定するだけで、デフォルトでGETリクエストが送信されます。

これは、最もシンプルな形式のHTTPリクエストであり、主にサーバーからデータを取得する際に使用されます。例えば、ブログ記事の一覧を取得したり、特定の商品情報を取得したりするようなシナリオで活躍します。

特別なオプションを指定する必要がないため、手軽に利用できるのが大きなメリットです。以下に、基本的なGETリクエストの例を示します。

// GETリクエストの例
fetch('https://api.example.com/data')
  .then(response => response.json()) // レスポンスをJSONとしてパース
  .then(data => console.log(data)) // パースされたデータを処理
  .catch(error => console.error('Error:', error)); // エラーハンドリング

この例では、`https://api.example.com/data`からJSONデータを取得し、コンソールに出力しています。

クエリパラメータの追加

GETリクエストで特定の条件に基づいてデータを取得したい場合、URLにクエリパラメータを追加します。例えば、「カテゴリが’tech’で、IDが123のデータ」といった指定です。クエリパラメータはURLの末尾に`?`をつけ、`キー=値`の形式で記述し、複数ある場合は`&`で連結します。

動的にクエリパラメータを構築するには、`URLSearchParams`オブジェクトが非常に便利です。これを使えば、パラメータのエンコード(特殊文字の変換)を自動的に行ってくれるため、手動でURLを構築する際のミスを防げます。

// クエリパラメータを追加したGETリクエストの例
const params = new URLSearchParams({
  id: '123',
  category: 'tech',
  sort: 'desc'
});

fetch(`https://api.example.com/products?${params.toString()}`)
  .then(response => response.json())
  .then(data => console.log(data))
  .catch(error => console.error('Error:', error));
// 生成されるURL: https://api.example.com/products?id=123&category=tech&sort=desc

この方法により、複雑なクエリでも安全かつ簡単にURLを生成し、サーバーへリクエストを送信できます。

レスポンスの処理

GETリクエストを送信した後、サーバーからのレスポンスを適切に処理することが重要です。`fetch()`が返すPromiseが解決されると、`Response`オブジェクトが渡されます。このオブジェクトには、HTTPステータス、ヘッダー、レスポンスボディなど、リクエストに関する詳細情報が含まれています。

レスポンスボディは通常、JSON、テキスト、HTML、画像などの形式で提供されます。Fetch APIでは、これらの異なる形式のレスポンスボディを解析するための便利なメソッドが用意されています。

最も頻繁に利用されるのは、`.json()`メソッドです。これはレスポンスボディをJSONとして解析し、JavaScriptオブジェクトに変換するPromiseを返します。その他に、プレーンテキストを扱う`.text()`、バイナリデータを扱う`.blob()`や`.arrayBuffer()`などがあります。

fetch('https://api.example.com/settings')
  .then(response => {
    if (!response.ok) {
      throw new Error(`HTTP error! status: ${response.status}`);
    }
    return response.json(); // JSONとして解析
  })
  .then(settings => {
    console.log('ユーザー設定:', settings);
  })
  .catch(error => console.error('設定取得エラー:', error));

これらのメソッドを適切に使うことで、サーバーからのあらゆる種類のデータを柔軟に処理できるようになります。

Fetch APIでPOSTリクエストを送信する方法:JSONとFormData

JSON形式でのPOSTリクエスト

ウェブアプリケーションでは、ユーザーが入力したデータ(フォームデータ、設定情報など)をサーバーに送信する際にPOSTリクエストがよく使われます。Fetch APIでJSON形式のデータをPOSTする際は、いくつかの重要な設定が必要です。

まず、`fetch()`メソッドの第二引数であるオプションオブジェクトに、`method: ‘POST’`を指定し、リクエストメソッドがPOSTであることを明確にします。次に、`headers`プロパティで'Content-Type': 'application/json'を設定し、送信するボディがJSON形式であることをサーバーに伝えます。

そして、`body`プロパティには、送信したいJavaScriptオブジェクトをJSON.stringify()で文字列化したものを指定します。これにより、JavaScriptオブジェクトがサーバーが理解できるJSON形式の文字列に変換されます。(参考情報より)

// POSTリクエストの例(JSON形式)
const userData = {
  name: '太郎',
  email: 'taro@example.com',
  age: 30
};

fetch('https://api.example.com/users', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
  },
  body: JSON.stringify(userData), // JavaScriptオブジェクトをJSON文字列に変換
})
.then(response => response.json())
.then(data => console.log('ユーザー登録成功:', data))
.catch(error => console.error('ユーザー登録エラー:', error));

この手順を踏むことで、クライアントサイドからサーバーへ安全かつ正確にJSONデータを送信することができます。

FormDataを使ったファイル送信・フォームデータ送信

Fetch APIでフォームデータやファイルを送信する際、FormDataオブジェクトは非常に便利なツールです。これは、キーと値のペアとしてデータを構築し、HTTPリクエストのボディとして送信するのに役立ちます。(参考情報より)

FormDataオブジェクトは、HTMLの“要素から直接作成することも可能です。これにより、フォーム内のすべての入力フィールド(テキスト、チェックボックス、ファイルなど)のデータを自動的に収集できます。

// FormData を使った POST リクエストの例
const myForm = document.getElementById('myForm');
myForm.addEventListener('submit', async (event) => {
  event.preventDefault(); // デフォルトのフォーム送信をキャンセル

  const formData = new FormData(myForm); // フォームからFormData オブジェクトを作成
  formData.append('extraField', 'additionalValue'); // 追加のフィールドも簡単に追加可能

  try {
    const response = await fetch('https://api.example.com/submit-form', {
      method: 'POST',
      body: formData, // FormDataオブジェクトを直接指定
    });
    const result = await response.json();
    console.log('Success:', result);
  } catch (error) {
    console.error('Error:', error);
  }
});

`FormData`を`body`に指定した場合、`fetch`は自動的に`Content-Type`ヘッダーをmultipart/form-dataに設定します。これにより、ファイルを含む複雑なフォームデータも適切にサーバーへ送信できます。(参考情報より)

Content-Typeヘッダーの重要性

`Content-Type`ヘッダーは、HTTPリクエストにおいて非常に重要な役割を果たします。これは、リクエストのボディに含まれるデータの種類(メディアタイプ)をサーバーに伝えるためのものです。

サーバーは、この`Content-Type`ヘッダーに基づいて、受信したボディデータをどのように解釈し、処理するかを決定します。例えば、JSONデータを送信する際には`application/json`を指定し、サーバー側でJSONパーサーが起動されることを期待します。

もし、`Content-Type`ヘッダーが正しく設定されていない場合、サーバーはデータを正しく解釈できず、エラーを返したり、予期せぬ動作を引き起こしたりする可能性があります。

よく使われる`Content-Type`ヘッダーの値には以下のようなものがあります。

  • application/json: JSON形式のデータを送信する場合。
  • application/x-www-form-urlencoded: 伝統的なHTMLフォームデータをURLエンコードして送信する場合。
  • multipart/form-data: ファイルを含むフォームデータを送信する場合(`FormData`を使用すると自動で設定されます)。
  • text/plain: プレーンテキストを送信する場合。

適切な`Content-Type`ヘッダーを設定することは、クライアントとサーバー間の円滑な通信を確保するために不可欠です。

Fetch APIのレスポンスを処理する:thenとjson

Promiseチェーンでの処理

Fetch APIはPromiseベースであるため、レスポンスの処理にはPromiseチェーンが利用されます。`fetch()`がPromiseを返した後、`.then()`メソッドを連鎖させることで、非同期処理の各段階を順序立てて実行できます。

最初の`.then()`ブロックでは、`fetch()`から返された`Response`オブジェクトを受け取ります。この段階で、HTTPステータスコードを確認したり、ヘッダー情報を参照したりすることができます。ここで、レスポンスが正常であるか(`response.ok`が`true`か)をチェックし、エラーがあれば早期に`throw`することで、以降の処理を中断させることができます。

次の`.then()`ブロックでは、最初の`.then()`から返されたPromiseが解決した結果(例えば、`.json()`メソッドによってパースされたJavaScriptオブジェクト)を受け取ります。ここで実際に取得したデータをアプリケーションのロジックに組み込む処理を行います。

fetch('https://api.example.com/articles')
  .then(response => {
    if (!response.ok) {
      throw new Error(`リクエスト失敗: ${response.status}`);
    }
    return response.json(); // 次のthenにパース結果を渡す
  })
  .then(articles => {
    console.log('記事リスト:', articles);
    // 取得した記事を表示する処理など
  })
  .catch(error => {
    console.error('データの取得中にエラーが発生しました:', error);
  });

`.catch()`メソッドは、チェーン内のどのPromiseがrejectされても、そのエラーを捕捉して処理する役割を担います。これにより、統一されたエラーハンドリングが可能になります。

レスポンスオブジェクトのプロパティ

`fetch()`から返される`Response`オブジェクトには、サーバーからの応答に関する豊富な情報が含まれています。これらのプロパティを理解することで、より詳細なレスポンスのチェックと処理が可能になります。

主要なプロパティとその説明を以下の表にまとめました。

プロパティ 説明
ok HTTPステータスコードが200-299の範囲内であればtrue、そうでなければfalse。(参考情報より) boolean
status HTTPステータスコード(例: 200, 404, 500)。(参考情報より) number
statusText ステータスコードに対応するテキスト(例: “OK”, “Not Found”)。 string
headers レスポンスヘッダーを含むHeadersオブジェクト。 Headers
url リクエストが送信された最終的なURL(リダイレクトを考慮)。 string
type レスポンスの種類(例: “basic”, “cors”, “opaque”)。 string

これらのプロパティを活用することで、レスポンスの状態を細かく把握し、ビジネスロジックに応じた適切な処理を行うことができます。

レスポンスボディの解析(.json(), .text(), .blob()など)

`Response`オブジェクトを受け取った後、次に必要なのはレスポンスボディの中身を読み取ることです。Fetch APIは、様々な形式のボディを解析するためのメソッドを提供しています。

最もよく使われるのは`.json()`メソッドです。これは、レスポンスボディをJSONとして解析し、結果をJavaScriptオブジェクトとして返すPromiseを返します。APIから構造化されたデータを取得する際に不可欠です。(参考情報より)

テキストデータを受け取る場合は`.text()`メソッドを使用します。これは、レスポンスボディをプレーンテキスト文字列として解析するPromiseを返します。HTMLコンテンツやシンプルなメッセージを取得するのに適しています。

画像や音声などのバイナリデータを扱う場合は、`.blob()`メソッド`.arrayBuffer()`メソッドが役立ちます。`.blob()`はデータを`Blob`オブジェクトとして返し、ファイル操作やオブジェクトURLの生成に利用できます。`.arrayBuffer()`は低レベルのバイナリデータを`ArrayBuffer`として取得します。

fetch('https://api.example.com/document.pdf')
  .then(response => response.blob()) // PDFをBlobとして取得
  .then(blob => {
    const fileURL = URL.createObjectURL(blob);
    console.log('ダウンロード可能なURL:', fileURL);
    // 例: <a href="fileURL" download="document.pdf">ダウンロード</a>
  })
  .catch(error => console.error('ファイル取得エラー:', error));

これらのボディ解析メソッドはすべてPromiseを返すため、`.then()`でチェーンし、非同期でデータを処理していくことになります。用途に応じて適切なメソッドを選択することが重要です。

Fetch APIでよくあるエラーと対処法:使えない?Timeoutは?

ネットワークエラーとHTTPエラー

Fetch APIを使用する際、大きく分けて二種類のエラーが発生する可能性があります。それはネットワークエラーHTTPエラーです。これらを区別して適切に対処することが重要です。

ネットワークエラーは、ブラウザがサーバーにリクエストを送信できない場合に発生します。例えば、インターネット接続がない、CORSポリシーに違反した、DNS解決に失敗した、などのケースです。このような場合、`fetch()`が返すPromiseはrejectされ、`.catch()`ブロックで捕捉できます。(参考情報より)

一方、HTTPエラーは、サーバーがリクエストを受け取り処理したものの、何らかの問題で正常なレスポンスを返せなかった場合に発生します。例えば、404 Not Found(リソースが見つからない)、500 Internal Server Error(サーバー内部エラー)などです。この場合、`fetch()`のPromiseはrejectされず、`.then()`ブロックが実行され、`Response`オブジェクトが返されます。HTTPエラーを検出するには、`response.ok`プロパティが`false`であることや、`response.status`プロパティの値を確認する必要があります。(参考情報より)

fetch('https://api.example.com/nonexistent-resource')
  .then(response => {
    if (!response.ok) { // HTTPエラー (例: 404) を検出
      throw new Error(`HTTPエラー発生: ${response.status}`);
    }
    return response.json();
  })
  .catch(error => { // ネットワークエラー または 上記でthrowされたエラーを捕捉
    console.error('リクエスト失敗:', error.message);
  });

この区別を理解しておくことで、エラーの発生源に応じたデバッグとリカバリが可能になります。

タイムアウト設定

Fetch APIには、残念ながら直接的なタイムアウトオプションは組み込まれていません。リクエストが長時間応答しない場合、ユーザーエクスペリエンスが低下したり、リソースが無駄に消費されたりする可能性があります。

しかし、AbortControllerと`setTimeout`を組み合わせることで、Fetchリクエストにタイムアウト機能を実装することができます。`AbortController`は、Fetchリクエストを中断するためのシグナルを生成するAPIです。

以下の例では、5秒後にリクエストが完了しない場合、`AbortController`を使ってリクエストを中断し、エラーを発生させています。

const controller = new AbortController();
const signal = controller.signal;
const timeout = 5000; // 5秒

const timeoutId = setTimeout(() => {
  controller.abort(); // タイムアウトしたらリクエストを中断
  console.warn('Fetchリクエストがタイムアウトしました。');
}, timeout);

fetch('https://api.example.com/slow-data', { signal })
  .then(response => {
    clearTimeout(timeoutId); // 成功したらタイムアウトを解除
    if (!response.ok) {
      throw new Error(`HTTP error! status: ${response.status}`);
    }
    return response.json();
  })
  .then(data => console.log('データ取得成功:', data))
  .catch(error => {
    if (error.name === 'AbortError') {
      console.error('リクエストが中止されました (タイムアウトまたは手動中断)。');
    } else {
      console.error('Fetchエラー:', error);
    }
  });

この手法により、リクエストが応答しない状態から適切に回復し、ユーザーに適切なフィードバックを提供できるようになります。

CORSの問題と解決策

CORS (Cross-Origin Resource Sharing)は、Webブラウザのセキュリティ機能の一つで、異なるオリジン(ドメイン、プロトコル、ポートのいずれかが異なる)間でリソースを共有する際の制限を設けています。

開発中にAPIを呼び出す際、「Access to fetch at ‘…’ from origin ‘…’ has been blocked by CORS policy」のようなエラーメッセージに遭遇することはよくあります。これは、ブラウザがセキュリティ上の理由から、異なるオリジンへのリクエストをブロックしていることを意味します。

CORSエラーの主な解決策は、サーバーサイドでの設定です。サーバー側で`Access-Control-Allow-Origin`ヘッダーを設定し、クライアントからのリクエストを許可するように設定する必要があります。例えば、特定のオリジンのみを許可したり、`*`を指定してすべてのオリジンからのリクエストを許可したりします。

開発環境では、ブラウザのCORS制限を一時的に無効にする拡張機能を使ったり、プロキシサーバーを立ててリクエストを転送したりする方法もありますが、これらは本番環境では推奨されません。

CORSはWebセキュリティの重要な側面であるため、その仕組みを理解し、サーバーサイドと協力して適切に設定することが、スムーズなAPI連携には不可欠です。