JavaScript | 非同期(fetch / async)でのエラーの落とし穴(良い書き方・悪い書き方)を具体コード比較で示す

JavaScript
スポンサーリンク

ここでは、「非同期処理(fetch / async)」でよくあるエラーの落とし穴を、
「悪い書き方 → なぜダメか → 良い書き方」に整理して解説します。

初心者がつまずきやすいポイントを中心に、
実際のコードとコメントで具体的に見ていきましょう。


前提:fetch() はエラーを「投げない」ことがある!

まず基本ルールです。

const res = await fetch("https://example.com/api");
JavaScript

この fetch() は、

  • ネットワークに失敗した場合 → TypeError が発生(例:オフライン、DNS失敗など)
  • HTTPエラー(404, 500 など)例外は発生しない!(成功扱い、res.ok が false になるだけ)

つまり、

「fetch に失敗した」の意味が、通信エラーなのか、HTTPエラーなのかで挙動が違う!


❌ 悪い書き方① — await fetch() を try/catch で囲んでるだけ

async function loadData() {
  try {
    const res = await fetch("https://example.com/api/data");
    const data = await res.json();
    console.log("成功:", data);
  } catch (e) {
    console.error("fetchに失敗しました:", e);
  }
}
JavaScript

🪲 落とし穴:

  • サーバーが 404500 を返しても、ここでは catch に入らない
  • res.okfalse のまま res.json() へ進んでしまい、
    → JSON が空やHTMLの場合に別のエラー(SyntaxError)が発生。
  • 結果、「どの段階で失敗したのか」がわからなくなる。

✅ 良い書き方① — res.ok を必ずチェック!

async function loadData() {
  try {
    const res = await fetch("https://example.com/api/data");

    // ✅ HTTPエラー(404など)を自分で検出して throw
    if (!res.ok) {
      throw new Error(`HTTPエラー: ${res.status}`);
    }

    const data = await res.json();
    console.log("成功:", data);
  } catch (e) {
    // ネットワークエラーも HTTPエラーもここでまとめて処理
    console.error("データ取得失敗:", e.message);
    showUserMessage("サーバーとの通信に失敗しました。");
  }
}
JavaScript

🔎 ポイント

  • res.ok(HTTP ステータス 200〜299 のとき true)をチェックして、
    HTTPエラーを「自分で例外化」する。
  • これにより、通信・HTTPどちらの失敗も一括でキャッチ可能。

❌ 悪い書き方② — async関数内で throw しても呼び出し側で無視

async function getUser() {
  const res = await fetch("/api/user");
  if (!res.ok) throw new Error("ユーザー取得失敗");
  return res.json();
}

// ❌ 呼び出し側で try/catch を書いてない!
getUser();
console.log("続きの処理…");
JavaScript

🪲 落とし穴:

  • getUser() 内でエラーが起きても、呼び出し元が await していないため、
    例外は非同期のPromise内部で握りつぶされる
  • コンソールには「Unhandled Promise Rejection」の警告が出るだけ。

✅ 良い書き方② — 呼び出し側も await + try/catch

async function getUser() {
  const res = await fetch("/api/user");
  if (!res.ok) throw new Error("ユーザー取得失敗");
  return res.json();
}

async function main() {
  try {
    const user = await getUser();
    console.log("ユーザー:", user);
  } catch (e) {
    console.error("getUser失敗:", e.message);
  }
}

main();
JavaScript

💡 ポイント

  • async 関数が返すのは常に Promise
  • その結果を await しないと例外が外に出てこない。
  • つまり「関数の中も外も両方で try/catch を意識」する必要がある。

❌ 悪い書き方③ — .then().catch() と async/await を混ぜる

async function fetchData() {
  const res = await fetch("/api/data")
    .then(res => res.json())
    .catch(err => console.error("fetch失敗:", err));

  console.log("結果:", res);
}
JavaScript

🪲 落とし穴:

  • .catch()fetch() の Promise だけに反応してしまい、
    その後の JSON 処理などで起きたエラーは拾えない。
  • await の中で .then() を混ぜると制御が分かりづらく、デバッグしにくい。

✅ 良い書き方③ — どちらかに統一(async/await推奨)

async function fetchData() {
  try {
    const res = await fetch("/api/data");
    if (!res.ok) throw new Error(`HTTP ${res.status}`);
    const data = await res.json();
    return data;
  } catch (e) {
    console.error("fetchData 失敗:", e.message);
    return null;
  }
}
JavaScript

シンプルで追いやすく、
エラーの種類も一箇所でまとめて処理できる。


⚠️ さらによくある初級者の誤り

❌ try…catch で非同期を囲んでも効かないパターン

try {
  fetch("/api/data"); // ← awaitしてない
} catch (e) {
  console.error("効かない:", e); // ← 絶対にここには来ない
}
JavaScript

fetch() は非同期関数なので、例外はPromise内部で発生。
try…catch は同期処理にしか効かない!

✅ 対応策:必ず await または .catch() を使う

try {
  const res = await fetch("/api/data");
} catch (e) {
  console.error("catchできる:", e);
}
JavaScript

実務的ベストプラクティス(まとめ)

目的ベストプラクティス
HTTP エラーif (!res.ok) throw new Error(...)
ネットワークエラーtry…catch で捕捉
呼び出し側でも捕捉await を使う
表示メッセージ開発者向け(console)とユーザー向け(UI)を分ける
複数APIを順に呼ぶ各APIごとに try/catch を設けて影響範囲を限定する

練習問題

次のコードを改善して、
HTTPエラーもキャッチできるようにしなさい。

async function fetchTodo() {
  try {
    const res = await fetch("https://example.com/api/todo");
    const data = await res.json();
    return data;
  } catch (e) {
    console.error("fetch失敗:", e);
    return [];
  }
}
JavaScript

✅ 解答例

async function fetchTodo() {
  try {
    const res = await fetch("https://example.com/api/todo");

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

    const data = await res.json();
    return data;

  } catch (e) {
    console.error("データ取得失敗:", e.message);
    return [];
  }
}
JavaScript
タイトルとURLをコピーしました