JavaScriptのfetchで404や500のレスポンスが返ってきても、それだけではPromiseがrejectされず、通常はcatchへ入りません。fetchはHTTPエラーのResponseも受け取るためです。HTTPステータスをエラーとして扱いたい場合は、response.okを調べ、falseなら自分でErrorをthrowします。ネットワーク障害やAbortによる失敗と、サーバーが返した404・500は区別して考えましょう。
404・500でもfetchのcatchに入らない理由
fetchはHTTP通信に成功してResponseを受け取れた場合、ステータスが404や500でもPromiseをfulfilledにする仕様です。一方、ネットワーク障害やリクエストが中断された場合などはrejectされ、catchへ到達することがあります。
| 発生した状態 | fetchのPromise | 対応 |
|---|---|---|
| HTTP 200 | 通常fulfilled | response.ok=true |
| HTTP 404 | 通常fulfilled | response.ok=falseを確認 |
| HTTP 500 | 通常fulfilled | response.ok=falseを確認 |
| ネットワーク・CORS等の通信失敗 | rejectされ得る | catchで処理 |
| AbortControllerによる中断 | AbortError等としてrejectされ得る | 中断として区別 |
用語解説:Response.ok
レスポンスのHTTPステータスが200から299の範囲ならtrue。それ以外のステータスはfalseになる読み取り専用プロパティです。
誤解しやすいNGコード
次の例ではサーバーが404を返しても、fetchそのものがrejectしないため、catchへ入らず「成功」として処理を進める可能性があります。
async function loadUser() {
try {
const response = await fetch("/api/users/42");
console.log("取得成功", response.status);
} catch (error) {
console.error("リクエスト失敗", error);
}
}
try/catchは「HTTPステータスが400以上なら自動的に例外が飛ぶ仕組み」ではありません。HTTPの成功・失敗を業務ロジックとして判定する処理を明示する必要があります。
response.okを使ってHTTPエラーをthrowする
async function fetchJson(url) {
const response = await fetch(url);
if (!response.ok) {
throw new Error("HTTP " + response.status);
}
if (response.status === 204) {
return null;
}
return await response.json();
}
async function showUser() {
try {
const user = await fetchJson("/api/users/42");
console.log(user);
} catch (error) {
console.error("API取得エラー", error);
}
}
response.okがfalseのとき自分でthrowするため、呼び出し元のcatchで404・500をまとめて扱えます。204 No Contentの場合は本文がないため、サンプルではjson()を呼ばずnullを返します。
response.json()も非同期です。サーバーが不正なJSONを返した場合などは解析エラーになり得るため、HTTPチェックだけで本文の正当性を保証できるわけではありません。
404と500はエラーの意味を分ける
画面での対応はステータスによって変わります。例えば404は対象リソースなし、401は認証の問題、500はサーバー内部の問題などを示します。ユーザー向け表示や再試行条件はAPI仕様に合わせて決めます。
async function getProfile() {
const res = await fetch("/api/profile");
if (res.status === 404) {
return null; // 未登録として扱う設計の例
}
if (res.status === 401) {
throw new Error("認証が必要です");
}
if (!res.ok) {
throw new Error("HTTP " + res.status);
}
return res.json();
}
404を正常な「未登録」と扱うか例外にするかはAPIの契約によります。すべてのHTTPエラーを同じ文言・同じリトライ処理にまとめると、原因が追いにくくなります。
ネットワークエラーとCORSは別の問題
サーバーが404を返すケースと、ブラウザがCORS制約でレスポンスを読み取れないケースは区別が必要です。CORSの失敗では通常、JavaScriptから目的のHTTPステータスや本文を読めません。
- ブラウザのNetworkタブで実際にHTTPレスポンスが返っているかを確認する。
- コンソールにCORSポリシー違反が出ていないか見る。
- AbortControllerを使う処理ならユーザー操作によるキャンセルと障害を分ける。
- 障害内容を画面へそのまま出しすぎず、ログに機密情報や個人データを残さない。
CORSの回避をフロントエンドで無理に行おうとせず、対象API側の許可設定・認証・リクエスト元を確認することが基本です。
ステータス処理をテストするポイント
APIが常に200を返す環境だけで確認すると、404・500の画面表示を見落としやすくなります。fetchをモック化するか、テスト用サーバーでステータス別の動作を検証します。
const response404 = new Response(
JSON.stringify({ message: "Not found" }),
{ status: 404, headers: { "Content-Type": "application/json" } }
);
console.log(response404.ok); // false
console.log(response404.status); // 404
上記のResponseの生成例は仕様を確認するサンプルコードで、実環境で自動テストを実行した記録ではありません。実際の通信失敗はfetchのPromiseのrejectも含めて別に検証します。
| 試験ケース | 期待する処理 |
|---|---|
| 200 + 正常JSON | 通常データを表示 |
| 204 + 空本文 | json()を呼ばず空データとして扱う |
| 404 | 必要に応じて未登録表示またはthrow |
| 500 | サーバーエラーとして記録・案内 |
| ネットワーク切断 | catch経由の失敗処理 |
| 不正JSON | 解析エラーを捕捉 |
axiosとfetchの違いも確認する
axiosは既定でHTTPステータスによる失敗をPromiseのrejectとして扱うのに対し、fetchではresponse.okの明示判定が必要です。この仕様差が、ライブラリを切り替えた際にエラー処理が変わる典型的な理由です。
(axiosとfetchのエラー処理の違いについては『axiosとfetchどっちを選ぶ?API連携で失敗しない選定フローと実践コード』をご参照ください)
(非同期通信の基本については『JavaScriptのAjax完全入門|fetchの使い方・CORSエラーの対処・非同期通信の基本を解説』をご参照ください)
(CORS原因の切り分けについては『JavaScriptでCORSエラーが出たときの原因と対処法を徹底解説』をご参照ください)
よくある疑問とまとめ
fetchが404を返すとthenへ入りますか?
通常は入ります。HTTPステータスが404でもResponseを受け取れればfetchのPromiseはfulfilledです。response.okで判定してください。
response.okのtrueは何番から何番ですか?
200から299までのHTTPステータスです。その他のステータスではfalseになります。
catchに入るのはいつですか?
ネットワーク失敗などでPromiseがrejectされた場合や、処理中に自分でErrorをthrowした場合、JSON解析で例外が起きた場合などです。
fetchはHTTPレスポンスの404・500を自動的には例外にしません。response.okとstatusでHTTPレベルの結果を判定し、ネットワーク失敗やJSON解析失敗はtry/catchで扱う、と役割を分けましょう。
まずは現在のfetchコードでresponse.okを確認しているか、404時にどう動くかをテストしてみてください。