JavaScript | 非同期例外をデバッグで追う

JavaScript JavaScript
スポンサーリンク

ここでは、「非同期処理(fetch / async / Promise)で起きたエラーをデバッグで追う」 を、
初心者にもわかるように ― 例題つきで ― 解説します。


目的

「非同期の中で発生したエラー(例外)」を stack trace(スタックトレース) から正確にたどる力をつける。


1. まず「stack trace(スタックトレース)」とは?

スタックトレースとは、

「エラーがどの関数の中で、どの行で起きたのか」
を示すエラーログの履歴です。


例:同期エラーの場合

function a() {
  b();
}
function b() {
  throw new Error('問題が発生しました');
}
a();
JavaScript

出力例:

Uncaught Error: 問題が発生しました
    at b (main.js:4)
    at a (main.js:2)
    at main.js:6

読み方:

  • 一番上が「エラーが発生した箇所」
  • 下に行くほど「その関数を呼び出した場所(呼び出しの履歴)」

2. 非同期(Promise / fetch)の場合はどうなる?

async function getData() {
  const res = await fetch('https://wrong.url/api'); // ← ここで失敗
  const data = await res.json();
  return data;
}

async function main() {
  await getData();
}

main();
JavaScript

ブラウザコンソール出力:

Uncaught (in promise) TypeError: Failed to fetch
    at getData (main.js:2)
    at async main (main.js:7)

ポイント:

  • エラー名:TypeError: Failed to fetch(ネットワークに失敗)
  • 「Uncaught (in promise)」 は、「Promise の中で投げられたけど、catch されなかった」ことを意味します。
  • async/await のスタックも追える(at async main)。

❌ よくある落とし穴:スタックが途中で消える!

function fetchData() {
  return fetch('https://wrong.url/api')
    .then(res => res.json())
    .then(data => {
      throw new Error('JSON解析で失敗');
    });
}

fetchData().catch(e => console.error(e));
JavaScript

出力例:

Error: JSON解析で失敗
    at main.js:5

😨 あれ?どこから呼ばれたか分からない…
Promiseチェーン(then/catch) では、
 JavaScriptエンジンが非同期境界でスタックを切り離すため、
 呼び出し元の履歴が失われやすい のです。


対策1:async/awaitで書く(stack traceが繋がる)

async function fetchData() {
  const res = await fetch('https://wrong.url/api');
  const data = await res.json();
  throw new Error('JSON解析で失敗');
}

async function main() {
  await fetchData();
}

main().catch(e => console.error(e));
JavaScript

出力例:

Error: JSON解析で失敗
    at fetchData (main.js:4)
    at async main (main.js:8)

✨ 非同期の呼び出し関係(main → fetchData)がスタックトレースに残ります。
async/await の方がデバッグしやすい理由のひとつ!


対策2:Error にスタック情報を明示的に保持

async function doTask() {
  try {
    const res = await fetch('https://wrong.url/api');
    return await res.json();
  } catch (err) {
    console.error('内部エラー:', err.stack);
    throw new Error('外部呼び出し側に再スロー');
  }
}

doTask().catch(e => console.error('呼び出し側:', e.stack));
JavaScript

.stack プロパティをログに出すことで、
「関数階層+行番号」がはっきり確認できます。


対策3:開発ツールでブレークポイント+スタック追跡

ブラウザ(例:Chrome DevTools, Firefox DevTools)での実践:

  1. Sources(ソース)タブを開く
  2. async function 内の行(たとえば await fetch の行)にブレークポイントを置く
  3. 再読み込みまたは関数呼び出しで停止する
  4. 右側の Call Stack を見る
    • main()getData() のような呼び出し階層が確認できる
  5. Scope パネルで変数の中身を確認できる

💡 非同期の「await」境界で止まるので、
ネットワークエラーやJSONパースエラーもその場で調査可能。


参考:Node.jsでのデバッグも同様

Node.jsで --inspect オプションを使えば同様に追えます。

node --inspect-brk app.js

VSCode で「Run and Debug」を開始すると、
async 関数内のステップ実行や stack trace の確認が可能。


非同期例外デバッグのまとめ

状況問題解決策
then().catch() でしか書いていないstack trace が途切れるasync/await を使う
fetch の失敗がどこで起きたかわからないawait なしの非同期呼び出しawait を使って try...catch で囲む
catch 内で情報が少ないerr.stack を出していないconsole.error(err.stack)
どの関数で止まったか調べたい非同期関数が多層になっているDevTools の「Call Stack」を確認
呼び出し元まで原因を知りたい再スロー時に情報を失うthrow new Error('説明', { cause: err })(ES2022)で原因を保持

発展:Error の cause プロパティ(ES2022〜)

try {
  await fetch('https://wrong.url/api');
} catch (err) {
  throw new Error('API取得失敗', { cause: err });
}
JavaScript

後で:

try {
  await doSomething();
} catch (e) {
  console.error(e.message);      // API取得失敗
  console.error(e.cause.message); // 元のエラー(TypeError: Failed to fetch)
}
JavaScript

👉 エラーの「原因」をネストして追跡できるので、複雑な非同期処理のデバッグに非常に便利です。


まとめ(初心者が押さえるべきポイント)

  1. try...catch で非同期を捕まえるには await が必要
  2. スタックトレース(stack trace)を読む癖をつける
  3. 非同期処理は async/await で書く方がデバッグがしやすい
  4. ブラウザの DevTools の “Call Stack” で関数呼び出しをたどる
  5. 再スローする時は cause プロパティで元エラーを保持
タイトルとURLをコピーしました