JavaScript アドバンス

JS 非同期処理のデバッグ

1. 非同期処理のバグ

非同期(Asynchronous)のバグは、コードが「後で」実行されるため、特定が非常に困難です。
このセクションでは、fetch()Promiseasync/await をデバッグするための実用的な方法を紹介します。

非同期のデバッグ(Debugging)の本質は、「コードがどこで止まったか」を見つけ、次に「なぜ止まったか」を突き止めることにあります。

2. なぜ非同期のバグは「見えない」のか

非同期コードは、関数がすでにリターン(Return)した後に失敗することがよくあります。これが、何も起きていないように感じさせる原因です。

async function loadData() {
  let response = await fetch("missing.json");
  let data = await response.json();
  console.log(data);
}

loadData();
console.log("完了");

この場合、たとえ後から fetch が失敗したとしても、完了 というメッセージは先に出力されてしまいます。

3. ルール 1:必ずエラーをハンドリングする

未処理の Promise の拒否(Rejection)は、開発者を混乱させます。エラーは早い段階で、かつ明確に処理しましょう。

async function loadData() {
  try {
    let response = await fetch("missing.json");
    let data = await response.json();
    console.log(data);
  } catch (error) {
    // ネットワークエラーや JSON 解析エラーをここでキャッチします
    console.log(error);
  }
}

これにより、通信エラーやデータ形式の不備を即座に把握できます。

4. ルール 2:response.ok を確認する

fetch は、404 などの HTTP エラーが発生しても Reject されません。
そのため、必ず response.ok をチェックする必要があります。

async function loadData() {
  try {
    let response = await fetch("missing.json");

    if (!response.ok) {
      console.log("HTTP エラー:", response.status);
      return;
    }

    let data = await response.json();
    console.log(data);
  } catch (error) {
    console.log("ネットワークエラーが発生しました");
  }
}

この実装により、HTTP エラーとネットワーク自体のエラーを切り分けてデバッグできます。

5. ルール 3:適切な内容をログに出力する

Promise オブジェクトをログに出力することと、実際のデータをログに出力することは別物です。
まずは レスポンス(Response)とステータスを確認しましょう。

async function loadData() {
  let response = await fetch("data.json");
  console.log(response.status);
  console.log(response.headers.get("content-type"));
}

これにより、サーバーが期待通りの形式でデータを返しているかを確認できます。

6. Network タブを活用する

ブラウザの Network(ネットワーク)タブ は、すべてのリクエストを表示します。これは fetch の問題をデバッグする最も速い方法です。

  • リクエスト URL が正しいか確認する
  • ステータスコード を確認する
  • レスポンスボディ(Response body)を確認する
  • レスポンスの Content-Type を確認する

フェッチに関するバグの多くは、実は JavaScript のバグではありません。URL の指定ミスやサーバーのレスポンス不備が原因であることがほとんどです。

7. await 行にブレークポイントを設置する

await が記述されている行には、ブレークポイント(Breakpoint)を設定できます。
これにより、非同期ステップの前後で(Value)を検証できます。

async function loadData() {
  let response = await fetch("data.json");
  let data = await response.json();
  console.log(data);
}

最初の await 行にブレークポイントを設定し、ステップオーバー(Step over)しながら response の中身をインスペクト(検査)してください。

8. よくある非同期処理のエラー

await の付け忘れは、初心者が最も陥りやすいミスです。

間違った例

async function loadData() {
  let response = await fetch("data.json");
  let data = response.json(); // await が欠落しています
  console.log(data); // JSON ではなく Promise がログ出力されます
}

正しい例

async function loadData() {
  let response = await fetch("data.json");
  let data = await response.json();
  console.log(data);
}

9. Promise チェーンのデバッグ

Promise はチェーンのどこで失敗するか分かりません。
各ステップの間にログを挟むことで、どこで問題が発生しているかを特定できます。

fetch("data.json")
.then(function(response) {
  console.log("レスポンスを受信しました");
  return response.json();
})
.then(function(data) {
  console.log("データの解析が完了しました");
  console.log(data);
})
.catch(function(error) {
  console.log("失敗しました");
  console.log(error);
});

ネストされたコールバックよりも、このようにチェーンの最後に一つ catch() を置く方が、エラー管理は遥かに容易になります。