3
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

AIが書いた fetch のエラーハンドリング、try-catch だけだと404/500を見逃す件

3
Posted at

AIに「APIからデータ取ってきて」と頼むと、こういうコードが返ってくる

Claude Code や ChatGPT に「fetch でAPIからデータ取ってきて。エラー処理もつけて」とお願いすると、だいたいこんなコードが返ってきます。

async function getUser(id: string) {
  try {
    const res = await fetch(`/api/users/${id}`);
    const user = await res.json();
    return user;
  } catch (error) {
    console.error("取得に失敗しました", error);
    throw error;
  }
}

try-catch で囲まれていて、一見ちゃんとエラー処理されているように見えます。レビューでも「まあ大丈夫そう」と通してしまいがちです。

でも、このコードには落とし穴があります。サーバーが 404 や 500 を返したとき、この catch には入りません。 エラー扱いにならず、成功したかのように後続処理へ進んでしまいます。

なぜそうなるのか、Before → After のコード対比で見ていきます。


Before(悪い例): なぜ 404/500 を見逃すのか

もう一度、さっきのコードです。

async function getUser(id: string) {
  try {
    const res = await fetch(`/api/users/${id}`);
    // ❌ res が成功レスポンスか失敗レスポンスかを一切見ていない
    const user = await res.json();
    return user;
  } catch (error) {
    console.error("取得に失敗しました", error);
    throw error;
  }
}

問題の根っこは、fetch() の仕様にあります。

fetch() は、HTTPステータスが 404 や 500 でも Promise を reject しない(=例外を投げない)。
fetch() が reject するのは、ネットワークエラー(オフライン・DNS解決失敗・CORS違反など)や、リクエストが中断されたときだけ。

これは MDN にも明記されている挙動です。サーバーにリクエストが「届いて」レスポンスが「返ってきた」時点で、ステータスが何であれ fetch() の Promise は resolve(成功) します。404 だろうと 500 だろうと、「通信としては成功した」という扱いなのです。

つまり Before のコードでは、こういうことが起きます。

  • サーバーが 404 Not Found を返す
  • でも fetch() は reject しないので catch に入らない
  • そのまま res.json() に進む
  • 404 のレスポンスボディ(HTMLのエラーページや { "error": "not found" } など)を無理やり JSON パースしようとする
  • 運が良ければ res.json() がパースエラーで落ちる/運が悪いと { error: "not found" } を「ユーザーデータ」として返してしまう

後者が一番厄介です。エラーがエラーとして扱われず、変なデータが下流に流れていく。 バグの発生箇所と実際に問題が顕在化する箇所がズレるので、原因究明にすごく時間がかかります。

catch があるから安心、ではないんですね。catch が拾ってくれるのは「ネットワークが死んでるとき」だけで、「サーバーがエラーを返したとき」は素通りします。


After(良い例): res.ok を明示的にチェックする

正しくは、res.ok でHTTPレベルの成否を自分でチェックし、失敗なら自分で throw する ことです。

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

    // ✅ HTTPレベルの失敗(4xx/5xx)を明示的にチェックする
    // res.ok は status が 200〜299 のとき true
    if (!res.ok) {
      // status を含めて投げると、後で原因が追いやすい
      throw new Error(`APIエラー: ${res.status} ${res.statusText}`);
    }

    const user = await res.json();
    return user;
  } catch (error) {
    // ここにはネットワークエラー(fetch自体のreject)と
    // 上で自分が throw したHTTPエラーの両方が入る
    console.error("取得に失敗しました", error);
    throw error;
  }
}

ポイントは2つです。

  • res.ok: ステータスが 200〜299 のとき true、それ以外(404, 500 など)は false。これで「HTTPレベルで成功したか」を判定できる。
  • !res.ok のときに自分で throw: fetch は投げてくれないので、自分で投げてはじめて catch に入る。エラーメッセージに res.status を含めると、404 なのか 500 なのかがログで一目で分かる。

これで、try-catchネットワークエラー(fetch の reject)HTTPエラー(自分で throw した 4xx/5xx) の両方をカバーできるようになりました。

ステータス別に分岐したいなら

「404 は空扱いにしたいけど 500 は例外として投げたい」みたいに、ステータスごとに振る舞いを変えたいこともあります。その場合は res.status を見て分岐します。

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

    if (res.status === 404) {
      // 「見つからない」は正常系として扱う設計もあり
      return null;
    }
    if (!res.ok) {
      // 404以外の失敗(500 など)は例外にする
      throw new Error(`APIエラー: ${res.status} ${res.statusText}`);
    }

    return await res.json();
  } catch (error) {
    console.error("取得に失敗しました", error);
    throw error;
  }
}

res.ok(成功か否かのざっくり判定)と res.status(具体的な番号)を使い分けるのがコツです。


実務でのひとこと

いくつか補足しておきます。

  • async/await でも .then() でも挙動は同じ。 どちらで書いても、fetch が 4xx/5xx で reject しないのは変わりません。.then() 派でも res.ok チェックは必要です。
  • axios は挙動が違う。 axios はデフォルトで 4xx/5xx を受け取ると Promise を reject します(catch に入る)。なので「axios の感覚で fetch を書くと res.ok チェックを忘れる」という取りこぼしが起きやすい。ライブラリを乗り換えたときは特に注意です。
  • HTTPステータスの範囲: 1xx 情報 / 2xx 成功 / 3xx リダイレクト / 4xx クライアント側エラー / 5xx サーバー側エラー。res.oktrue になるのは 2xx だけです。

AIにfetchを書かせるときのレビュー観点

AIコード生成と付き合ううえで、fetch は「一見正しく見えて実は抜けがある」典型例です。生成されたコードをそのまま信じず、res.ok のチェックが入っているかを目視する のをレビュー項目に入れておくと事故が減ります。

抜けていたら、こう追加でお願いすると堅くなります。

このfetchのコードに、res.ok のチェックとHTTPエラー(404/500)の分岐を追加してください。エラー時は res.status をメッセージに含めて throw してください。ネットワークエラーの try-catch はそのまま残してください。

AI は「動くコード」は速く出しますが、「HTTPの失敗をどう扱うか」みたいな仕様の判断は、指示しないと最小限で済ませがちです。ここを人間側のレビュー観点として持っておくと、プログラミングそのものと、AIにどう指示すれば堅いコードが返ってくるかという協働スキルが同時に身につきます。

fetch の1行から、AIとの付き合い方まで地続きだったりします。


中上級者の方には当たり前すぎる内容なので、初心者の知り合いへの紹介や、社内研修・後輩育成の資料の参考にでもどうぞ。

未経験から Next.js + Supabase + Claude Code で Web アプリを公開するまで を、手を動かしながら学べる導線を用意しています。

このあたりの「AIが書いたコードのどこを疑うか」は、教材の中でも実際のプロンプトと一緒に扱っています。

3
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?