Що містить stack trace
Stack trace, це рядок у властивості error.stack, що містить тип помилки, її повідомлення і ланцюжок викликів функцій, який привів до збою. Кожен рядок at ... показує одну функцію, її файл, рядок і стовпець.
Теорія
TL;DR
- Стек викликів (call stack), це структура, де рушій тримає перелік функцій, що зараз виконуються.
- Коли створюється помилка, рушій робить «знімок» цього стека і кладе його в
error.stack. - Перший рядок
stack, цеName: message, далі йдуть рядкиat функція (файл:рядок:стовпець). - Порядок читання: згори місце помилки, нижче ті, хто його викликав.
- Асинхронні виклики (
setTimeout, промиси,await) можуть «розірвати» стек, бо виконуються в іншій ітерації event loop.
Швидкий приклад
function a() {
b();
}
function b() {
c();
}
function c() {
throw new Error("Something went wrong!");
}
try {
a();
} catch (err) {
console.log(err.stack);
}Вивід у консолі:
Error: Something went wrong!
at c (<anonymous>:8:9)
at b (<anonymous>:4:3)
at a (<anonymous>:1:3)
at <anonymous>:12:3Що таке стек викликів
Стек викликів (call stack), це структура даних, у якій JavaScript зберігає інформацію про те, які функції зараз виконуються і в якому порядку їх було викликано.
Коли виникає помилка, рушій створює «знімок» цього стека, той самий stack trace, що показує, де саме і в якій послідовності було викликано код, який привів до помилки.
Як читати рядки stack trace
Кожен рядок у stack, це один рівень виклику функції. Розберемо приклад вище:
Error: Something went wrong! <- тип помилки і повідомлення
at c (<anonymous>:8:9) <- функція c() кинула помилку
at b (<anonymous>:4:3) <- c() було викликано з b()
at a (<anonymous>:1:3) <- b() було викликано з a()
at <anonymous>:12:3 <- a() викликано в глобальній областіКожен рядок вказує:
- ім'я функції (
at c,at b,at a); - файл (або
<anonymous>, якщо коду немає імені файлу); - номер рядка і стовпця, де стався виклик.
Де живе стек: властивість .stack
Коли створюється об'єкт помилки (new Error()), у нього автоматично з'являється властивість .stack, яка містить:
- назву помилки (
Error,TypeErrorтощо); - повідомлення (
message); - трасування викликів (stack trace).
const err = new Error("Network failure");
console.log(err.stack);Вивід:
Error: Network failure
at <anonymous>:1:13У реальній обробці помилок зручно логувати обидві частини окремо:
try {
throw new Error("Unexpected failure");
} catch (e) {
console.error("Error:", e.message);
console.error("Call stack:\n", e.stack);
}Власні помилки теж мають стек. Якщо ви створюєте свій клас через class ... extends Error, властивість .stack буде доступна так само:
class ValidationError extends Error {
constructor(message) {
super(message);
this.name = "ValidationError";
}
}
try {
throw new ValidationError("Invalid email");
} catch (e) {
console.log(e.stack);
}Вивід:
ValidationError: Invalid email
at <anonymous>:8:9
at ...Стек при асинхронних помилках
Асинхронні виклики (через setTimeout, Promise, await) можуть «розірвати» стек, бо виконуються пізніше, в іншій ітерації event loop.
function a() {
setTimeout(() => {
throw new Error("Timer failure");
}, 0);
}
a();Вивід:
Uncaught Error: Timer failure
at Timeout._onTimeout (<anonymous>:3:11)Тут видно лише частину стека, бо помилка сталася вже в іншому контексті виконання. Функції
a()в трасуванні немає: на момент спрацювання таймера її кадр зі стека вже зник.
Практичне застосування
Stack trace потрібен, щоб:
- швидко знаходити місце збою в коді;
- розуміти, які функції привели до помилки;
- зберігати трасування в логах і системах моніторингу (Sentry, GlitchTip, Logtail);
- будувати власний бекенд для аналізу помилок користувачів.
Складові помилки в одному місці:
| Елемент | Що це означає |
|---|---|
Error.message | Текст помилки |
Error.name | Тип помилки (TypeError, ReferenceError, ...) |
Error.stack | Повний шлях викликів до місця помилки |
Кожен рядок at ... | Одна функція в ланцюжку викликів |
| Порядок | Згори місце помилки, нижче той, хто її викликав |
Типові помилки
- Вважати
error.stackчастиною стандарту з гарантованим форматом. Це де-факто угода, і текст відрізняється між рушіями, тож парсити його ненадійно. - Думати, що стек фіксується під час
throw. Насправді він записується у моментnew Error(...), тому створювати помилку заздалегідь і кидати пізніше, погана ідея. - Логувати лише
e.messageі втрачатиe.stack, після чого шукати причину збою немає за чим. - Кидати рядок або об'єкт замість
Error, у такого значення стека немає взагалі. - Дивуватися короткому стеку в асинхронному коді. Передавайте початкову помилку далі через
cause, щоб не втратити контекст.
Коротка відповідь
Для співбесідиКоротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.