🐷

【JavaScript】スタックトレースの読み方

に公開
node inorderTraversalTest.js を実行した結果
/Users/mavo/project/algorithm-solutions/binary-tree/problems/10_inorderTraversal/js/src/inorderTraversal.js:10
        arr.push(node.data);
            ^

TypeError: Cannot read properties of undefined (reading 'push')
    at inorderTraversalHelper (/Users/mavo/project/algorithm-solutions/binary-tree/problems/10_inorderTraversal/js/src/inorderTraversal.js:10:13)
    at inorderTraversalHelper (/Users/mavo/project/algorithm-solutions/binary-tree/problems/10_inorderTraversal/js/src/inorderTraversal.js:9:9)
    at inorderTraversal (/Users/mavo/project/algorithm-solutions/binary-tree/problems/10_inorderTraversal/js/src/inorderTraversal.js:2:12)
    at Object.<anonymous> (/Users/mavo/project/algorithm-solutions/binary-tree/problems/10_inorderTraversal/js/tests/inorderTraversalTest.js:6:13)
    at Module._compile (node:internal/modules/cjs/loader:1369:14)
    at Module._extensions..js (node:internal/modules/cjs/loader:1427:10)
    at Module.load (node:internal/modules/cjs/loader:1206:32)
    at Module._load (node:internal/modules/cjs/loader:1022:12)
    at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:135:12)
    at node:internal/main/run_main_module:28:49

スタックトレースの読み方の基本

エラーメッセージは上から下に「さかのぼる」形で表示されます。
つまり、一番上の行が「エラーが実際に起きた場所」で、
その下に行くほど「その関数を呼び出した関数」が順に書かれています。

→ スタックトレースでは、最初に呼び出された関数は一番下に表示されます。

エラーメッセージの内容

TypeError: Cannot read properties of undefined (reading 'push')

  • undefined なものに対して .push をしようとしてエラー

  • どこで? → 次の行がその答え

at inorderTraversalHelper (/.../inorderTraversal.js:10:13)

  • 実際に .push を呼び出した行(=最初に壊れた場所)

  • /inorderTraversal.js の 10行目、13文字目

  • この時点で result が undefined だったと思われる

at inorderTraversal (/.../inorderTraversal.js:2:12)

  • この関数は再帰的に呼び出されていた
    → 9行目で inorderTraversalHelper() を呼んだ

at inorderTraversal (/.../inorderTraversal.js:2:12)

  • inorderTraversal 関数から inorderTraversalHelper を呼び出した(多分 result を渡し忘れた)

at Object.<anonymous> (.../inorderTraversalTest.js:6:13)

  • テストファイルで inorderTraversal() を呼び出していた

  • テストの6行目から実行が始まった

時系列で見ると

  1. テストファイル(inorderTraversalTest.js)の6行目で inorderTraversal() を呼び出した

  2. その中で inorderTraversalHelper() を呼び出した(2行目)

  3. さらにその中で inorderTraversalHelper() を再帰呼び出しした(9行目)

  4. 最終的に result.push(...) を実行しようとして result が undefined → エラー発生(10行目)

Node.jsが.jsファイルを実行するまで

    at Module._compile (node:internal/modules/cjs/loader:1369:14)
    at Module._extensions..js (node:internal/modules/cjs/loader:1427:10)
    at Module.load (node:internal/modules/cjs/loader:1206:32)
    at Module._load (node:internal/modules/cjs/loader:1022:12)
    at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:135:12)
    at node:internal/main/run_main_module:28:49
  1. ユーザーが node yourScript.js を実行

  2. Node.js がファイルを探す

  3. ファイルを読み込んで、モジュールとして「コンパイル」する

  4. モジュールとして実行する(require() や ES Modules にも対応)

at Module._compile (node:internal/modules/cjs/loader:1369:14)

ファイル内容(JavaScriptコード)を function にラップして実行する

Node.js は .js ファイルをそのまま実行せず、以下のように変換します

(function(exports, require, module, __filename, __dirname) {
  // 自分のファイルのコードがここに来る
});
  • これにより、require や module.exports がローカルで使えるようになります

  • _compile() はこの「ラップ & 実行」を行なっている関数です

at Module._extensions..js (node:internal/modules/cjs/loader:1427:10)

.js ファイルをどう処理するか定義している関数

Node.js は拡張子ごとにロード方法を変える

  • .js → JavaScriptとして読み込み
  • .json → JSONとして読み込み
  • .node → ネイティブ拡張として読み込み

_extensions['.js'] によって .js ファイルは _compile() に渡されます

at Module.load (node:internal/modules/cjs/loader:1206:32)

モジュールをロード(=読み込み準備)する

  • モジュールがすでにキャッシュにあるかチェック
  • なければファイルを読み込み _extensions に任せて処理
  • exports を返す準備をする

at Module._load (node:internal/modules/cjs/loader:1022:12)

モジュールの実体を取得して返す

  • キャッシュの再確認
  • Module.load() を呼ぶ
  • 最終的に module.exports を返す

at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:135:12)

ユーザーが指定した .js ファイルを実行するエントリーポイント

  • ここが「Node.js の起動処理」→「ユーザーのコードを実行」への橋渡し
  • 最初の .js ファイルを require() で呼び出す(CommonJS)

at node:internal/main/run_main_module:28:49

Node.js が node yourScript.js を処理しはじめる最初の地点

  • コマンドライン引数からユーザーファイルを決定
  • executeUserEntryPoint() を呼んで起動

全体の流れ

$ node app.js
↓
run_main_module         ← スクリプトの開始点
↓
executeUserEntryPoint   ← ユーザーのファイルを起動
↓
Module._load            ← モジュールを取得
↓
Module.load             ← ロード処理
↓
Module._extensions.js   ← .js ファイルとして扱う
↓
Module._compile         ← 関数ラップして実行
↓
(あなたのコードが実行される)

Discussion