JavaScript 逆引き集 | fetch POST(JSON)

JavaScript JavaScript
スポンサーリンク

fetch で HTTP POST(JSON)の基本と実践

fetch は HTTP リクエストを送る API。JSON を POST する場合は、メソッド指定・本文の JSON 化・ヘッダー指定(Content-Type)を組み合わせます。レスポンスの確認とエラーハンドリングまで含めるのが定石です。


基本の書き方

const url = "https://api.example.com/items";
const data = { title: "JavaScript入門", price: 1200 };

const r = await fetch(url, {
  method: "POST",
  headers: {
    "Content-Type": "application/json", // 本文がJSONであることを宣言
    "Accept": "application/json",       // 返却もJSONを期待(任意)
  },
  body: JSON.stringify(data),           // オブジェクト→JSON文字列に変換
});

if (!r.ok) throw new Error(`HTTP ${r.status}`);
const json = await r.json();
console.log(json);
JavaScript
  • ポイント: 本文は必ず JSON.stringify(...) で文字列化。Content-Type: application/json を付けるとサーバー側で JSON として扱われます。

すぐ使えるテンプレート集

テンプレート1: 安全な POST(HTTPチェック+JSON受け取り)

async function postJSON(url, payload) {
  const r = await fetch(url, {
    method: "POST",
    headers: { "Content-Type": "application/json", "Accept": "application/json" },
    body: JSON.stringify(payload),
  });
  if (!r.ok) throw new Error(`HTTP ${r.status}`);
  return r.json();
}

// 使用例
const result = await postJSON("/api/books", { title: "JS", author: "Aki" });
console.log(result);
JavaScript

テンプレート2: タイムアウト付き(AbortController)

async function postWithTimeout(url, payload, ms = 3000) {
  const ac = new AbortController();
  const timer = setTimeout(() => ac.abort(), ms);

  try {
    const r = await fetch(url, {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify(payload),
      signal: ac.signal,
    });
    clearTimeout(timer);
    if (!r.ok) throw new Error(`HTTP ${r.status}`);
    return await r.json();
  } catch (e) {
    if (e.name === "AbortError") throw new Error("Timeout");
    throw e;
  }
}
JavaScript
  • ポイント: fetch はデフォルト無期限。締め切りは AbortController を使います。

テンプレート3: 認証トークン付き

async function postAuth(url, payload, token) {
  const r = await fetch(url, {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      "Authorization": `Bearer ${token}`,
      "Accept": "application/json",
    },
    body: JSON.stringify(payload),
  });
  if (!r.ok) throw new Error(`HTTP ${r.status}`);
  return r.json();
}
JavaScript

テンプレート4: 成功・失敗を allSettled で集約

const payloads = [{ a: 1 }, { a: 2 }, { a: 3 }];
const settled = await Promise.allSettled(
  payloads.map(p => fetch("/api/bulk", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify(p),
  }).then(r => r.ok ? r.json() : Promise.reject(new Error(`HTTP ${r.status}`))))
);
// 成功だけ抽出
const ok = settled.filter(r => r.status === "fulfilled").map(r => r.value);
const ng = settled.filter(r => r.status === "rejected").map(r => r.reason);
JavaScript

実務でのポイント

  • ヘッダーと本文の整合性: JSON を送るなら本文は JSON.stringify、ヘッダーは Content-Type: application/json をセット。受け取りも JSON なら Accept: application/json を付けると意図が明確。
  • HTTP エラーチェック: response.ok(2xx)を確認し、失敗は例外化して共通処理へ流すのがシンプル。
  • CORS とサーバ設定: クロスオリジンでの POST はサーバー側の CORS 設定が必須(プリフライトも発生しうる)。ブラウザ側だけでは回避不能。
  • JSON 以外の送信: 画像やファイル、フォーム送信は FormDataBlob/ArrayBuffer を選択。JSON と使い分ける。

よくある落とし穴と対策

  • オブジェクトをそのまま body に入れる: 文字列化されず送信形式が不正。
    • 対策: 必ず JSON.stringify(data)
  • Content-Type を付けない: サーバーが JSON と認識できず 400/415 になる。
    • 対策: headers: { "Content-Type": "application/json" }
  • HTTP 失敗を見落とす: 2xx 以外は ok=false
    • 対策: if (!r.ok) throw new Error(...) で明示処理。
  • タイムアウト未設定: フリーズの原因。
    • 対策: AbortController で締め切り。
  • 直接 JSON 化できない型:Date/Map 等はそのままでは想定外の文字列に。
    • 対策: 送信前に整形(文字列化・オブジェクト化)。

練習問題(手を動かして覚える)

// 1) 基本POST(HTTPチェック)
async function ex1() {
  const r = await fetch("/api/echo", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ msg: "hello" }),
  });
  if (!r.ok) throw new Error(`HTTP ${r.status}`);
  console.log(await r.json());
}

// 2) 認証トークン付きPOST
async function ex2() {
  const token = "demo-token";
  const r = await fetch("/api/me", {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      "Authorization": `Bearer ${token}`,
      "Accept": "application/json",
    },
    body: JSON.stringify({ action: "whoami" }),
  });
  if (!r.ok) throw new Error(`HTTP ${r.status}`);
  console.log(await r.json());
}

// 3) タイムアウト(2秒)付きPOST
async function ex3() {
  const ac = new AbortController();
  const t = setTimeout(() => ac.abort(), 2000);
  try {
    const r = await fetch("/api/slow", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ n: 1 }),
      signal: ac.signal,
    });
    clearTimeout(t);
    if (!r.ok) throw new Error(`HTTP ${r.status}`);
    console.log(await r.json());
  } catch (e) {
    console.log(e.name === "AbortError" ? "timeout" : e.message);
  }
}

// 4) まとめてPOSTして部分成功を扱う
async function ex4() {
  const payloads = [{ id: 1 }, { id: -1 }, { id: 2 }];
  const settled = await Promise.allSettled(payloads.map(p =>
    fetch("/api/items", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify(p),
    }).then(r => r.ok ? r.json() : Promise.reject(new Error(`HTTP ${r.status}`)))
  ));
  const ok = settled.filter(r => r.status === "fulfilled").map(r => r.value);
  const ng = settled.filter(r => r.status === "rejected").map(r => r.reason.message);
  console.log({ ok, ng });
}
JavaScript
タイトルとURLをコピーしました