JavaScript / LESSON 16 / JS16

JavaScript:fetchとJSON

1学習の目的

2基礎解説

やりたいこと書き方備考
データを取得const res = await fetch(url);Promise を返す
成功を確認if (!res.ok) { … }必須。404は例外にならない
JSONに変換const data = await res.json();これにも await
データを送るfetch(url, { method: "POST", … })第2引数でオプション指定
✅ 覚えるべき重要ポイント
  1. 404 や 500 でも fetch は失敗しない。「サーバーから返事が来た」時点で成功扱いになる。res.okres.status自分で確認する必要がある。
  2. await2回必要。1回目は通信の完了を待ち、2回目は本文の読み取りを待つ。res.json() にも await を付け忘れないこと。
  3. catch に飛ぶのはネットワークに繋がらないときだけ(オフライン、URLの誤り、ドメインが存在しない)。エラーの種類は TypeError
  4. データを送るときは methodheadersbody を指定する。body は JSON.stringify() で文字列にしてから渡す。
  5. 壊れたJSONが返ると res.json()SyntaxError を投げる。サーバーがHTMLのエラーページを返すことは実際にあるので、try で囲む。

現場使用例:ユーザー情報の取得、商品一覧の読み込み、フォーム内容の送信、天気や為替の表示。現代のWebアプリは、ほぼすべてこの仕組みで動いている

async function getUser(id) {
  try {
    const res = await fetch(`https://api.example.com/users/${id}`);

    // ここが最重要:404でも fetch 自体は成功する
    if (!res.ok) {
      throw new Error(`取得失敗(${res.status})`);
    }

    const data = await res.json();   // ← ここにも await
    return data;

  } catch (e) {
    console.log(`エラー:${e.message}`);
    return null;
  }
}

// データを送る場合
await fetch("https://api.example.com/users", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({ name: "田中" }),
});

3基本ドリル(10問)

JS16-D01 API + "/users/1" からデータを取得し、name を出力せよ(API は基準URLが入った定数とする)。 コーディング★☆☆無料
期待される結果

出力 == 田中

ヒント

await fetch(...) で応答を取り、await res.json() でデータに変換する。await が2回必要。

模範解答
async function main() {
  const res = await fetch(`${API}/users/1`);
  const data = await res.json();
  console.log(data.name);
}

main();
解説

await が2回必要なのがポイント。1回目は通信の完了、2回目は本文の読み取りを待っている。

JS16-D02 存在しないURLを fetch して 404 が返ったとき、どうなるか。
A. catch に飛ぶ B. 成功扱いになり res.ok が false になる C. null が返る D. 自動で再試行される
選択★☆☆無料
期待される結果

解答 == B

ヒント

「サーバーから返事が来た」ことをどう解釈するか。

模範解答
B
解説

fetch が失敗と見なすのはネットワークに繋がらないときだけ。404も500も「返事が来た」ので成功扱いになる。だから res.ok の確認が必須。

JS16-D03 API + "/users/1" を取得し、ステータスコードと ok を「200 / true」の形式で出力せよ。 コーディング★☆☆無料
期待される結果

出力 == 200 / true

ヒント

res.status と res.ok をそのまま使う。json() は不要。

模範解答
async function main() {
  const res = await fetch(`${API}/users/1`);
  console.log(`${res.status} / ${res.ok}`);
}

main();
解説

res.okステータスが200番台なら true。細かい番号を見る前に、まずこれで成否を判定するのが簡潔。

JS16-D04 応答をJSONに変換するコードを完成させよ。 穴埋め★☆☆無料
コード
async function main() {
  const res = await fetch(`${API}/users/1`);
  const data = await res.____();
  console.log(data.name);
}

main();
期待される結果

出力 == 田中

ヒント

JSON形式で読み取るメソッド。

模範解答
json
解説

文字列として読みたいときは res.text()どちらも await が必要で、本文の読み取り自体が非同期処理になっている。

JS16-D05 fetch で catch に飛ぶのはどんなときか。
A. 404が返ったとき B. 500が返ったとき C. ネットワークに繋がらないとき D. JSONが空のとき
選択★☆☆無料
期待される結果

解答 == C

ヒント

サーバーまで届いたかどうかが分かれ目。

模範解答
C
解説

オフライン、URLのつづりミス、ドメインが存在しない——そもそも通信が成立しない場合だけ。エラーの種類は TypeError になる。

JS16-D06 API + "/nothing"(404が返る)を取得し、res.ok が false なら「取得失敗(404)」と出力せよ。 コーディング★★☆無料
期待される結果

出力 == 取得失敗(404)

ヒント

if (!res.ok) で判定し、res.status を埋め込む。

模範解答
async function main() {
  const res = await fetch(`${API}/nothing`);

  if (!res.ok) {
    console.log(`取得失敗(${res.status})`);
  }
}

main();
解説

この確認を書かないと、404のHTMLをJSONとして読もうとしてエラーになる。原因が分かりにくいバグになるので、必ず最初に書く。

JS16-D07 送信するデータを文字列に変換するコードを完成させよ。 穴埋め★★☆無料
コード
async function main() {
  const res = await fetch(`${API}/users`, {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.____({ name: "新規ユーザー" }),
  });

  console.log(res.status);
}

main();
期待される結果

出力 == 200

ヒント

オブジェクトをJSON文字列にするメソッド。末尾に fy が付く。

模範解答
stringify
解説

body には文字列しか渡せない。オブジェクトをそのまま渡すと [object Object] という文字列が送られてしまう。

JS16-D08 const data = res.json(); と await を付けずに書くと data に入るのはどれか。
A. データ本体 B. Promise C. 文字列 D. undefined
選択★★☆無料
期待される結果

解答 == B

ヒント

json() も非同期のメソッド。

模範解答
B
解説

JS15で学んだとおりawait を忘れると Promise が入るdata.name が undefined になったら、まず await の付け忘れを疑う。

JS16-D09 API + "/users"(配列が返る)を取得し、各ユーザーの名前を「1. 田中」の形式で出力せよ。 コーディング★★☆無料
期待される結果

出力2行。1. 田中 / 2. 鈴木

ヒント

返ってくるのは配列なので、そのまま forEach や for...of で回せる。

模範解答
async function main() {
  const res = await fetch(`${API}/users`);
  const users = await res.json();

  users.forEach((u, i) => {
    console.log(`${i + 1}. ${u.name}`);
  });
}

main();
解説

JSONの配列は、変換した時点でただのJavaScriptの配列になる。JS09の map や filter がそのまま使える。

JS16-D10 存在しないホスト "http://127.0.0.1:9999/x" に fetch し、catch で「通信できません」と出力せよ。 コーディング★★☆無料
期待される結果

出力 == 通信できません

ヒント

try / catch で囲む。ネットワークエラーは catch に飛ぶ。

模範解答
async function main() {
  try {
    await fetch("http://127.0.0.1:9999/x");
  } catch (e) {
    console.log("通信できません");
  }
}

main();
解説

catch に来るのはこのケースだけ。404などのHTTPエラーとは扱いが違うので、両方を書いて初めて通信処理が完成する。

4実践シナリオ(5問)

JS16-S01 【安全な取得関数】URLを受け取り、成功ならデータ、失敗なら null を返す関数 fetchJson を定義せよ。res.ok の確認と try/catch の両方を行うこと。API + "/users/1"API + "/nothing" の2件で試し、「田中」「取得できませんでした」の2行を出力すること。 コーディング★★☆無料
期待される結果

出力2行が完全一致

ヒント

!res.ok なら throw し、catch で null を返す。呼び出し側で null を判定する。

模範解答
async function fetchJson(url) {
  try {
    const res = await fetch(url);

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

    return await res.json();
  } catch (e) {
    return null;
  }
}

async function main() {
  const user = await fetchJson(`${API}/users/1`);
  console.log(user === null ? "取得できませんでした" : user.name);

  const missing = await fetchJson(`${API}/nothing`);
  console.log(missing === null ? "取得できませんでした" : missing.name);
}

main();
解説

成功か null かに単純化して返す設計。呼び出す側は通信の細かい事情を知らなくてよくなり、if 1つで分岐できる。実務でよく使われる形。

JS16-S02 【エラーの種類を見分ける】3つのURLにアクセスし、結果を「[users/1] 成功」「[nothing] HTTPエラー:404」「[bad-host] 通信エラー」の形式で出力せよ。
URLは API + "/users/1"API + "/nothing""http://127.0.0.1:9999/x"(ラベルは順に users/1、nothing、bad-host)。
コーディング★★☆無料
期待される結果

出力3行が完全一致

ヒント

try の中で res.ok を判定し、HTTPエラーは自分で出力する。catch に来たものだけが通信エラー。

模範解答
async function check(label, url) {
  try {
    const res = await fetch(url);

    if (!res.ok) {
      console.log(`[${label}] HTTPエラー:${res.status}`);
      return;
    }

    console.log(`[${label}] 成功`);
  } catch (e) {
    console.log(`[${label}] 通信エラー`);
  }
}

async function main() {
  await check("users/1", `${API}/users/1`);
  await check("nothing", `${API}/nothing`);
  await check("bad-host", "http://127.0.0.1:9999/x");
}

main();
解説

HTTPエラーと通信エラーは別物。前者はサーバーに届いたうえでの応答、後者はそもそも届いていない。監視やログでは、この2つを区別して記録する。

JS16-S03 【壊れたJSONへの対処】API + "/broken" は不正なJSONを返す。取得を試み、JSONの解析に失敗したら「JSONの解析に失敗しました」と出力せよ。そのあと正常な API + "/users/1" でも同じ処理を行い「田中」と出力すること。 コーディング★★☆無料
期待される結果

出力2行が完全一致

ヒント

res.json() が SyntaxError を投げるので、try で囲めば捕まえられる。

模範解答
async function load(url) {
  try {
    const res = await fetch(url);
    const data = await res.json();
    console.log(data.name);
  } catch (e) {
    console.log("JSONの解析に失敗しました");
  }
}

async function main() {
  await load(`${API}/broken`);
  await load(`${API}/users/1`);
}

main();
解説

サーバーがHTMLのエラーページを返すことは実際にある。ステータスが200でも中身が壊れている可能性があるので、json() も try の中に入れておく。

JS16-S04 【並列取得】API + "/users/1"API + "/users"Promise.all で並列に取得し、「ユーザー:田中」「一覧:2件」の2行を出力せよ。 コーディング★★★無料
期待される結果

出力2行が完全一致

ヒント

Promise.all の中で fetch と json() をまとめて行う関数を渡す。分割代入で受け取る。

模範解答
const fetchJson = async (url) => {
  const res = await fetch(url);
  return res.json();
};

async function main() {
  const [user, list] = await Promise.all([
    fetchJson(`${API}/users/1`),
    fetchJson(`${API}/users`),
  ]);

  console.log(`ユーザー:${user.name}`);
  console.log(`一覧:${list.length}件`);
}

main();
解説

互いに依存しない通信は並列にする(JS15)。順に await すると待ち時間が足し算になるが、Promise.all なら遅いほうの時間で済む。

JS16-S05 【データを送信する】API + "/users"{ name: "新規ユーザー", email: "new@example.com" } を POST で送り、ステータスが200番台なら「送信成功(200)」と出力せよ。Content-Type ヘッダーと JSON.stringify を正しく指定すること。 コーディング★★★無料
期待される結果

出力 == 送信成功(200)

ヒント

fetch の第2引数に { method, headers, body } を渡す。body は JSON.stringify で文字列にする。

模範解答
async function main() {
  const res = await fetch(`${API}/users`, {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({
      name: "新規ユーザー",
      email: "new@example.com",
    }),
  });

  if (res.ok) {
    console.log(`送信成功(${res.status})`);
  } else {
    console.log(`送信失敗(${res.status})`);
  }
}

main();
解説

3点セット(method・headers・body)がPOSTの基本形。Content-Type を書かないとサーバーがJSONだと認識せず、データを受け取れないことがある。

5仕上げ課題

JS16-FINAL 【ユーザー情報ダッシュボード】APIから複数のデータを取得し、レポートを出力せよ。

利用できるエンドポイント(API は基準URL):
/users … ユーザー配列 [{ id, name }, …](2件)
/users/1{ id, name, email }
/nothing … 404 が返る
/broken … 壊れたJSONが返る

仕様:
① 安全に取得する関数 safeFetch(url) を作る。成功なら { ok: true, data }、失敗なら { ok: false, reason } を返す
 reason は、HTTPエラーなら "HTTP 404" の形式、JSON解析失敗なら "不正なJSON"、通信不可なら "接続できません"
/users/users/1並列で取得し、成功したら一覧と詳細を表示
/nothing/broken も順に試し、失敗理由を表示
④ 最後に成功件数と失敗件数を出力

出力仕様(8行):
=== ユーザー情報 ===
[一覧] 2件(田中、鈴木)
[詳細] 田中 <tanaka@example.com>
---
[nothing] 失敗:HTTP 404
[broken] 失敗:不正なJSON
---
成功2件 / 失敗2件
期待される結果

出力8行が完全一致

ヒント

safeFetch では res.ok を確認してから res.json() を try で囲む。JSON解析の失敗と通信の失敗を区別するには、fetch と json() を別々の try で囲むか、エラーの種類(e.name === "SyntaxError")で判定する。名前の連結は map(u => u.name).join("、")。

模範解答
async function safeFetch(url) {
  let res;

  try {
    res = await fetch(url);
  } catch (e) {
    return { ok: false, reason: "接続できません" };
  }

  if (!res.ok) {
    return { ok: false, reason: `HTTP ${res.status}` };
  }

  try {
    const data = await res.json();
    return { ok: true, data };
  } catch (e) {
    return { ok: false, reason: "不正なJSON" };
  }
}

async function main() {
  console.log("=== ユーザー情報 ===");

  let ok = 0;
  let ng = 0;

  const [list, detail] = await Promise.all([
    safeFetch(`${API}/users`),
    safeFetch(`${API}/users/1`),
  ]);

  if (list.ok) {
    const names = list.data.map((u) => u.name).join("、");
    console.log(`[一覧] ${list.data.length}件(${names})`);
    ok++;
  }

  if (detail.ok) {
    console.log(`[詳細] ${detail.data.name} <${detail.data.email}>`);
    ok++;
  }

  console.log("---");

  for (const [label, url] of [["nothing", `${API}/nothing`], ["broken", `${API}/broken`]]) {
    const result = await safeFetch(url);

    if (!result.ok) {
      console.log(`[${label}] 失敗:${result.reason}`);
      ng++;
    }
  }

  console.log("---");
  console.log(`成功${ok}件 / 失敗${ng}件`);
}

main();
解説

通信で起きうる3種類の失敗を、すべて別々に扱えているのがこの課題の到達点。

接続できない(catch)② サーバーは応答したがエラー(res.ok が false)③ 応答したが中身が壊れている(json() が SyntaxError)——この3つを区別せずに catch ひとつでまとめてしまうと、「なぜ失敗したのか」が分からなくなる。ユーザーに表示すべきメッセージも、原因によって変わるはずだ。

そして safeFetch{ ok, data, reason } という決まった形で返している点も重要だ。呼び出す側は if (result.ok) と書くだけでよく、fetch の細かい事情を知らなくて済む。複雑さを関数の中に閉じ込める——JS08から一貫して学んできた設計の考え方が、ここでも効いている。

次章では、取得したデータをブラウザに保存する方法を学ぶ。JS12で作ったToDoアプリの「再読み込みすると消える」という弱点が、ついに解決する。