Error: Import failed указывает на строку создания ошибки. Открыв её, вы увидите, как приложение сообщает о сбое. Чтобы найти реальный сбой, проследите приложенный cause до парсера и изучите переданный ввод.
Скачайте полный пример стека JavaScript, распакуйте и перейдите в read-javascript-stack-trace. Он использует локальные файлы и встроенные модули Node без установки и сетевых запросов. Пример создан для руководства. Импорт читает и разбирает записи, нигде их не сохраняя. Все команды ниже выполнялись в Node.js 22.22.1; используйте Node.js 22 или новее.
Три исходных файла задают небольшой путь исследования:
| Файл | Работа | Что изучить |
|---|---|---|
run.mjs |
Выбирает ввод и ожидает импорт | Имя входного файла, результат, статус выхода |
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 бросает существующий объект. Внешний стек начинается со строки 10, поскольку Node записывает положение new Error(). Поздний throw того же объекта не сдвигает сохранённое место. Это описано в документации ошибок Node.js 22.
Блок try тоже важен: и readFile, и parseRecords могут завершиться сбоем с одинаковым внешним сообщением. Одно «Import failed» не определяет ветку. Например, тест отсутствующего файла получает причину ENOENT, а не SyntaxError.
Проследите причину до парсера и фактического ввода
Стек причины сначала указывает 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. См. справочник Node по error.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
Режим не передаёт cause в new Error. Обёртка больше не содержит ошибку парсера. Её стек не восстановит потерянную позицию ввода и не отличит сбой от ветки отсутствующего файла. Нужно другое наблюдение, например локальное воспроизведение с сохранённой исходной ошибкой.
Сохранение cause помогает исследованию, но не исправляет ввод: with-cause всё ещё падает, как показала первая команда.
Используйте асинхронные кадры как подсказки и заполняйте пробелы по коду
Оба стека содержат at async .../run.mjs:14:19 — ожидающий вызов. К моменту ошибки парсера readFile уже завершён; его работы нет в стеке, хотя результат передан парсеру.
Документация V8 по асинхронным стекам описывает восстановление вокруг await и некоторых операций Promise, включая Promise.all() и Promise.any(). Кадры отражают отдельные асинхронные связи, а не полную историю callback, таймеров или ввода-вывода. Отсутствующий кадр не доказывает, что работа не выполнялась.
Текст SyntaxError и доступные кадры зависят от версии Node/V8. Открывайте соответствующие исходники по указанным местам, не считайте запись универсальным форматом стека. Тесты проверяют полезные свойства ошибки и кадры приложения, а не всю строку стека.
Проверьте гипотезу повторной попытки на вводе
Рассмотрим утверждение: «Это временная проблема сети; повтор должен помочь». Это учебная гипотеза для проверки, а не цитата ИИ или реальный инцидент.
Стек показывает SyntaxError в JSON.parse. Исходники показывают байты локального файла. Сетевых операций в примере нет, а три попытки с теми же неверными байтами отклоняются. Подтверждений предложенного объяснения здесь нет.
Теперь запустите исправленную фикстуру, где удалена только лишняя запятая:
node run.mjs fixed
Она завершается статусом 0 и выводит:
Input: records.fixed.json
Parsed records: [{"id":"tea","count":2}]
Затем запустите регрессионные проверки:
node --test importer.test.mjs
Все девять тестов проходят. Они охватывают сохранённую причину парсера, обёртку с потерей, повторный сбой при прежнем вводе, успех исправленного ввода, отсутствующий файл, создание и поздний throw и три режима CLI. Две падающие демонстрационные команды — ожидаемые сбои, а не провал регрессионных тестов.
Это подтверждает объяснение для локальных байтов примера. В реальном импортёре проследите создателя неверного текста, прежде чем решать, почему он неверен. Пример проверяет синтаксис JSON и результат, но не схему записей и не сохранение импорта.
Читайте окружающий код в Yenpo, проверяйте в терминале
Откройте распакованную папку в Yenpo и по руководству чтения найдите importer.mjs, parser.mjs и выбранный ввод. Начните с указанных строк, затем прочитайте всю ветку catch и код, передающий input.
С настроенной моделью AI Reader Yenpo поможет изучить путь. Конкретный запрос:
Проследи ошибку Import failed от строки создания через cause до ввода JSON.parse. Укажи файлы и строки. Раздели показанное стеком, установленное исходниками и требующее запуска в терминале. Проверь, может ли повтор изменить байты в примере.
Выполните команды в терминале и сравните вывод с объяснением.