logo

Error の cause オプションによってエラーの再 throw 時にスタックトレースが失われるのを防ぐ

2025-11-24
6 days ago

開発環境

  • Node.js 18+

前提

JavaScript の Error コンストラクタには cause オプションがあり、再スロー(rethrow)する際に元のエラー情報を保持したまま、新しいエラーとして扱うことができます。

従来は throw new Error("xxx: " + error.message) のようにラップするとスタックトレースが失われる問題がありましたが、cause を利用することでスタックトレースを安全に伝搬できます。

本題

cause オプションとは

Error コンストラクタの第 2 引数として cause を渡すと「どのエラーに起因しているか」を表現できます。

try {
  doSomething();
} catch (err) {
  throw new Error("データ取得時に失敗しました", { cause: err });
}

これにより、ツールやランタイムによっては以下のように、スタックトレースがネストされた形で表示されます。

Error: データ取得時に失敗しました
  at fetchData …
Caused by: TypeError: fetch is not defined
  at doSomething …

従来のエラー再スローは元のトレースを失うため、原因調査に時間がかかる問題がありましたが、cause はこれを解消します。

ネストされた例

function parseJson(str) {
  try {
    return JSON.parse(str);
  } catch (err) {
    throw new Error("JSON パースに失敗しました", { cause: err });
  }
}

function loadConfig() {
  try {
    const data = readFileSync("./config.json", "utf8");
    return parseJson(data);
  } catch (err) {
    throw new Error("設定ファイルの読み込みに失敗しました", { cause: err });
  }
}

loadConfig();

最上位ではただの「設定読み込み失敗」だが、cause により

  • JSON のパースエラー
  • ファイル読み込みエラー

など、発生源まで遡って全てのスタックトレースを保持できます。

エラー分類としての cause 活用

cause は単にエラーの連鎖を作るだけでなく、任意の値を渡すことでエラーを分類する用途にも使えます

例:HTTP ステータスコードでエラーを分類

try {
  const res = await fetch("/api/data");
  if (!res.ok) {
    throw new Error("API エラー", { cause: res.status });
  }
} catch (err) {
  if (err.cause === 404) {
    console.error("データが見つかりませんでした");
  } else {
    console.error("予期せぬエラーが発生しました");
  }
}

⭕️ cause の利用が適切なケース

  • エラーをラップするが、元のスタックを必ず保持したい
  • ビジネスロジック層から詳細を引き剥がしつつ、デバッグ情報は残したい
  • API のレスポンスコードや独自エラーコードで分岐したい
  • try-catch のネストが深いアプリケーション構造(Next.js API Routes、Cloudflare Workers、バックエンドなど)


❌ 避けたほうがよいケース

  • 単純なエラー出力のみで、階層化が不要な場合
  • エラー発生場所が深すぎて cause のネストが逆に読みづらくなる場合

さいごに

Error の cause オプションは、従来のエラーラップによるスタックトレース紛失を防ぎ、エラーの原因追跡を大幅に改善する機能です。 また、{ cause: 404 }のように任意データを持たせることでエラー分類にも柔軟に利用できます。 例外処理の品質向上のために、JavaScript / TypeScript プロジェクトでは積極的に活用すべき仕組でしょう。

参照