Error: Import failed はエラーを作る行を指します。そこで分かるのはアプリの報告方法です。実際の失敗を探すには、付属する cause からパーサーへ進み、届いた入力を読みます。

JavaScript スタックトレースのラボを展開して read-javascript-stack-trace に移動します。ローカルファイルと Node の組み込みモジュールを使う教材で、インストールや通信はありません。import はデータを読み解析しますが保存はしません。以下は Node.js 22.22.1 で実行した結果です。22 以降を使ってください。

調べる経路は三つのファイルです。

ファイル 役割 確認するもの
run.mjs 入力を選び import を await 入力名、結果、終了状態
importer.mjs 読み込み、解析、失敗のラップ try と捕捉した cause
parser.mjs テキストを JSON.parse に渡す 呼び出しと渡したテキスト

失敗する入力を実行し、両方のスタックを残す

node run.mjs with-cause

このコマンドは終了状態 1 で失敗する想定です。次は stdout と stderr を合わせたもので、絶対ディレクトリ部分だけを file:///lab/ に置き換えました。ファイル名、行、列、メッセージは実行記録のものです。

Input: records.bad.json
Error: Import failed
    at importRecords (file:///lab/importer.mjs:10:19)
    at async file:///lab/run.mjs:14:19

Caused by:
SyntaxError: Expected double-quoted property name in JSON at position 29 (line 2 column 28)
    at JSON.parse (<anonymous>)
    at parseRecords (file:///lab/parser.mjs:2:15)
    at importRecords (file:///lab/importer.mjs:7:12)
    at async file:///lab/run.mjs:14:19

run.mjs は error.stack と error.cause.stack を別々に表示します。最初がラッパー、次が捕捉した SyntaxError です。エラー報告をコピーするときも区別を残してください。外側のメッセージしか保存しないロガーでは、ここで役立つ情報の大半が失われます。

importer.mjs:10:19 の末尾二つはソースの行と列です。パーサーの「line 2 column 28」は JSON 入力上の位置です。異なる座標なので、トレースを出したものと同じソース版を使い、各位置を対応するファイルや値に結び付けます。

生成行を開き、catch 分岐を読む

配布物の importer.mjs 全体です。

import { readFile } from 'node:fs/promises';
import { parseRecords } from './parser.mjs';

export async function importRecords(input, { preserveCause = true } = {}) {
  try {
    const text = await readFile(input, 'utf8');
    return parseRecords(text);
  } catch (cause) {
    const options = preserveCause ? { cause } : undefined;
    const error = new Error('Import failed', options);
    throw error;
  }
}

10 行目でラッパーを生成し、11 行目でそのオブジェクトを投げます。Node は new Error() の位置を記録するため、外側のスタックは 10 行目から始まります。後で同じオブジェクトを投げても、その位置は移動しません。Node.js 22 のエラー文書に説明があります。

try も重要です。readFile と parseRecords のどちらも失敗し得て、同じ外側メッセージになります。「Import failed」だけでは区別できません。例えばファイル欠落のテストは SyntaxError ではなく ENOENT の cause を持ちます。

cause からパーサーと実際の入力を追う

cause のスタックの先頭は JSON.parse です。読めるコードの最初のフレーム parser.mjs:2:15 へ進みます。

export function parseRecords(text) {
  return JSON.parse(text);
}

text を readFile(input, 'utf8') まで戻ります。run.mjs が選んだ records.bad.json の全体は次のとおりです。

[
  {"id": "tea", "count": 2,}
]

2 の後のカンマにより、パーサーは次のプロパティ名を期待しますが、報告位置で } に遭遇します。入力、操作、再現できる失敗が揃いました。ラッパーの生成行は調査の入口であり、メッセージを変えても JSON は変わりません。

new Error(message, { cause }) は捕捉した値を新エラーから参照できるようにします。Node の 16.9.0 で追加され、ここで使う 22.22.1 にも記載されています。値の型は任意なので、汎用の処理では .stack を読む前に型を確認します。このラボの表示処理は instanceof Error で判定します。error.cause のリファレンスも参照してください。

cause を外し、失われる情報を見る

node run.mjs without-cause

こちらも状態 1 で終了します。同じ入力が失敗しますが、末尾は次のようになります。

Error: Import failed
    at importRecords (file:///lab/importer.mjs:10:19)
    at async file:///lab/run.mjs:14:19

Cause: unavailable

このモードは new Error に cause を渡さず、ラッパーがパーサーのエラーを保持しません。そのスタックだけでは入力位置を復元したり、ファイル欠落と区別したりできません。元のエラーを残したローカル再現など、別の観察が必要です。

cause は調査できる情報を増やしますが、入力は直しません。最初のコマンドどおり、with-cause でも失敗します。

非同期フレームを手がかりにし、コードで間を埋める

両方の記録には await した呼び出し元 at async .../run.mjs:14:19 が含まれます。解析失敗時には readFile は解決済みで、入力を提供したにもかかわらずその作業はトレースに出ません。

V8 の非同期スタック文書は、await、Promise.all()、Promise.any() など一部の Promise 操作からの再構成を説明しています。選ばれた非同期関係を表すもので、コールバック、タイマー、I/O 全体の履歴ではありません。フレームがないことは未実行の証明にはなりません。

SyntaxError の文言やフレームは Node/V8 の版で変わり得ます。表示位置から対応するソースを開き、この記録を普遍的な形式とみなさないでください。テストもスタック全文ではなく、有用なエラーの属性とアプリのフレームを確認します。

再試行という仮説を入力で確かめる

「一時的なネットワーク問題なので再試行で直る」という仮説を考えます。教材用の仮説で、実際の AI 回答や障害からの引用ではありません。

トレースは JSON.parse の SyntaxError を示し、ソースではローカルファイルのバイトを解析しています。このラボにネットワーク操作はなく、同じ不正入力を 3 回試してもすべて拒否されます。この説明を支える証拠はありません。

末尾のカンマだけを除いた修正版を実行します。

node run.mjs fixed

終了状態 0 で、次を表示します。

Input: records.fixed.json
Parsed records: [{"id":"tea","count":2}]

回帰確認も実行します。

node --test importer.test.mjs

9 件すべて成功します。パーサー cause の保持、情報を失うラッパー、同じ入力での繰り返し失敗、修正入力の成功、欠落ファイル、生成と後の throw の位置、3 つの CLI モードを確認します。先ほどの二つの失敗コマンドは想定したデモで、回帰テストの失敗ではありません。

これでローカルの入力バイトについて説明できます。実際の importer なら、不正になった理由を判断する前にテキストの生成元まで追ってください。このラボは JSON 構文と結果を確認しますが、レコード schema の検証や取り込みの保存は行いません。

Yenpo で周辺を読み、ターミナルで検証する

展開したフォルダーを Yenpo で開き、コード読解ガイドを使って importer.mjs、parser.mjs、選ばれた入力を探します。報告行から catch 全体と、input を渡す呼び出し元へ進みます。

モデルを設定すれば Yenpo の AI Readerでも経路を調べられます。質問を絞る例です。

Import failed の生成行から cause、JSON.parse に渡る入力まで追い、ファイルと行を示してください。スタックが示すこと、ソースで確認できること、まだ実行が必要なことを分けてください。この例で再試行が入力バイトを変えるか確認してください。

ターミナルでコマンドを実行し、出力と説明を照合してください。