【JavaScript】fetch API の使い方|GET・POST・async/await・エラーハンドリング・AbortController・実務パターンまで解説

API からデータを取得する、フォームを非同期で送信する、ファイルをアップロードするなど、fetch API は JavaScript の HTTP 通信の基本です。Promise ベースでコードが読みやすく、async/await と組み合わせることで直感的に書けます。

この記事でわかること
・fetch で GET リクエストを送る基本
・async/await での書き方
・POST リクエスト(JSON / FormData)
・response.ok による HTTP エラーの正しい検出
・try/catch/finally のエラーハンドリング
・AbortController によるリクエストキャンセル・タイムアウト
・レスポンスの型(json / text / blob)
・API データの取得・フォーム送信・ファイルアップロードの実務パターン
スポンサーリンク

GET リクエストの基本

then チェーン版

JavaScript
fetch("https://api.example.com/users")
  .then(response => {
    if (!response.ok) throw new Error(`HTTP ${response.status}`);
    return response.json();
  })
  .then(data => console.log(data))
  .catch(error => console.error("エラー:", error));

async/await 版(推奨)

JavaScript
async function fetchUsers() {
  try {
    const response = await fetch("https://api.example.com/users");

    if (!response.ok) {
      throw new Error(`HTTP ${response.status}`);
    }

    const data = await response.json();
    console.log(data);
  } catch (error) {
    console.error("エラー:", error);
  }
}

fetchUsers();
async/await を使うと、非同期処理を同期処理のような見た目で書けます。try/catch でエラーハンドリングもシンプルになるため、新しいコードでは async/await が推奨です。

response.ok による HTTP エラーの検出

fetch はネットワークエラー以外では reject しません。404 や 500 などの HTTP エラーでも then に進みます。response.ok(ステータス 200〜299)を必ずチェックしてください。

状況 fetch の動作 catch で捕捉
ステータス 200 resolve(正常)
ステータス 404 resolve(response.ok === false × 捕捉されない
ステータス 500 resolve(response.ok === false × 捕捉されない
ネットワークエラー reject ○ 捕捉される
CORS エラー reject ○ 捕捉される
fetch の catch はネットワークエラーのみ捕捉します。HTTP 404 / 500 は catch に入らないため、if (!response.ok) throw new Error() で手動でエラーにする必要があります。これは fetch の最大の落とし穴です。

POST リクエスト

JSON を送信する

JavaScript
async function createUser(user) {
  const response = await fetch("https://api.example.com/users", {
    method: "POST",
    headers: {
      "Content-Type": "application/json"
    },
    body: JSON.stringify(user)
  });

  if (!response.ok) throw new Error(`HTTP ${response.status}`);
  return await response.json();
}

const newUser = await createUser({ name: "田中", email: "tanaka@example.com" });
console.log(newUser);

FormData を送信する

JavaScript
const form = document.getElementById("myForm");

form.addEventListener("submit", async (e) => {
  e.preventDefault();

  const response = await fetch("/api/submit", {
    method: "POST",
    body: new FormData(form) // Content-Type は自動設定される
  });

  if (!response.ok) throw new Error(`HTTP ${response.status}`);
  const result = await response.json();
  console.log(result);
});
FormData を body に渡す場合、Content-Type ヘッダーは設定しないでください。ブラウザが自動的に multipart/form-data と適切な boundary を設定します。手動で設定するとエラーになります。

レスポンスの型(json / text / blob)

メソッド 戻り値 用途
response.json() パースされた JavaScript オブジェクト JSON API のレスポンス
response.text() 文字列 HTML / XML / プレーンテキスト
response.blob() Blob オブジェクト 画像 / ファイルのダウンロード
response.arrayBuffer() ArrayBuffer バイナリデータ
response.formData() FormData multipart レスポンス
レスポンスの body は1 回しか読めませんresponse.json() を呼んだ後に response.text() を呼ぶとエラーになります。デバッグ用にテキストも見たい場合は response.clone().text() を使ってください。

fetch のオプション一覧

オプション 説明
method HTTP メソッド "GET" / "POST" / "PUT" / "DELETE"
headers リクエストヘッダー { "Content-Type": "application/json" }
body リクエストボディ JSON.stringify(data) / new FormData(form)
credentials Cookie の送信 "same-origin"(デフォルト)/ "include"(クロスオリジン)
signal AbortController の signal リクエストキャンセル用
mode CORS モード "cors"(デフォルト)/ "no-cors" / "same-origin"

AbortController でリクエストをキャンセルする

JavaScript
const controller = new AbortController();

async function fetchData() {
  try {
    const response = await fetch("/api/data", {
      signal: controller.signal
    });
    const data = await response.json();
    console.log(data);
  } catch (error) {
    if (error.name === "AbortError") {
      console.log("リクエストがキャンセルされました");
    } else {
      console.error("エラー:", error);
    }
  }
}

fetchData();

// 必要なタイミングでキャンセル
controller.abort();

タイムアウトを実装する

JavaScript
async function fetchWithTimeout(url, options = {}, timeout = 5000) {
  const controller = new AbortController();
  const timeoutId = setTimeout(() => controller.abort(), timeout);

  try {
    const response = await fetch(url, {
      ...options,
      signal: controller.signal
    });
    return response;
  } finally {
    clearTimeout(timeoutId);
  }
}

// 使用例: 5秒でタイムアウト
const res = await fetchWithTimeout("/api/slow-endpoint", {}, 5000);
fetch にはタイムアウト機能が組み込まれていないため、AbortController + setTimeout で自作します。サーバーの応答が遅い場合のフォールバックとして必須です。

実務でよく使うパターン

API データを取得して DOM に表示する

JavaScript
async function renderUsers() {
  const list = document.getElementById("userList");
  list.textContent = "読み込み中...";

  try {
    const res = await fetch("/api/users");
    if (!res.ok) throw new Error(`HTTP ${res.status}`);
    const users = await res.json();

    list.innerHTML = users
      .map(u => `<li>${u.name} (${u.email})</li>`)
      .join("");
  } catch (error) {
    list.textContent = "データの取得に失敗しました";
    console.error(error);
  }
}

renderUsers();

汎用 API クライアント関数

JavaScript
async function api(endpoint, options = {}) {
  const defaultHeaders = { "Content-Type": "application/json" };

  const response = await fetch(endpoint, {
    headers: { ...defaultHeaders, ...options.headers },
    ...options,
    body: options.body ? JSON.stringify(options.body) : undefined
  });

  if (!response.ok) {
    const error = await response.text();
    throw new Error(`HTTP ${response.status}: ${error}`);
  }

  return response.json();
}

// 使用例
const users = await api("/api/users");
const newUser = await api("/api/users", {
  method: "POST",
  body: { name: "田中", email: "tanaka@example.com" }
});

リトライ付き fetch

JavaScript
async function fetchWithRetry(url, options = {}, retries = 3) {
  for (let i = 0; i < retries; i++) {
    try {
      const res = await fetch(url, options);
      if (res.ok) return res;
      if (res.status < 500) throw new Error(`HTTP ${res.status}`); // 4xx はリトライしない
    } catch (error) {
      if (i === retries - 1) throw error;
      await new Promise(r => setTimeout(r, 1000 * (i + 1))); // 指数バックオフ
    }
  }
}

関連記事

よくある質問

Qfetch で 404 エラーが catch されません。
Afetch は HTTP エラー(404 / 500)では reject しません。response.ok をチェックして、false の場合は手動で throw new Error() してください。catch が捕捉するのはネットワークエラーと CORS エラーのみです。
Qfetch と axios の違いは何ですか?
Afetch はブラウザ組み込みで追加インストール不要。axios はライブラリで、自動 JSON 変換・HTTP エラーの自動 reject・リクエストインターセプターなどの機能があります。シンプルな通信なら fetch で十分、複雑な API クライアントなら axios が便利です。
QFormData を送信するときに Content-Type を設定してはいけないのはなぜですか?
AFormData を body に渡すと、ブラウザが multipart/form-data と適切な boundary を自動設定します。手動で Content-Type を設定すると boundary が含まれず、サーバーがリクエストを正しくパースできなくなります。
Qfetch にタイムアウト機能はありますか?
A組み込みのタイムアウト機能はありません。AbortController + setTimeout で自作します。setTimeout(() => controller.abort(), 5000) のように設定してください。
Qfetch で Cookie を送信するにはどうすればよいですか?
A同一オリジンではデフォルトで Cookie が送信されます。クロスオリジンで Cookie を送信するには credentials: "include" を設定し、サーバー側でも Access-Control-Allow-Credentials: true を返す必要があります。

まとめ

fetch API の使い方を整理しました。

  • 基本: await fetch(url) + response.json()
  • HTTP エラー検出: if (!response.ok) throw new Error()(最重要!)
  • POST: method: "POST" + body: JSON.stringify(data)
  • FormData: body: new FormData(form)(Content-Type 設定不要)
  • キャンセル: AbortControllersignal オプション
  • タイムアウト: AbortController + setTimeout で自作

response.ok のチェックを忘れると、404 や 500 エラーが見逃されるという落とし穴があります。fetch を使う際は必ず response.ok を確認し、エラー時には適切な UI フィードバック(エラーメッセージの表示など)を実装しましょう。