Обработка ошибок в JavaScript всегда была немного хаотичной. Получить ошибку легко, но отследить ее первоисточник бывает очень сложно. Именно здесь и приходит на помощь свойство cause.
❯ Проблема классической обработки ошибок
В многоуровневом коде (например, сервисы, вызывающие другие сервисы, функции-обертки, «всплывающие» ошибки и т.д.) легко потерять нить того, что именно пошло не так. Обычно код при этом выглядит примерно так:
try {
JSON.parse('{ bad json }');
} catch (err) {
throw new Error('Something went wrong: ' + err.message);
}
Да, ошибка обернута, но при этом утрачены исходный стек вызовов и тип ошибки.
❯ Представляем Error.cause
Свойство cause позволяет аккуратно сохранить исходную ошибку:
try {
try {
JSON.parse('{ bad json }');
} catch (err) {
throw new Error('Something went wrong', { cause: err });
}
} catch (err) {
console.error(err.stack);
console.error('Caused by:', err.cause.stack);
}
Вот что происходит при использовании Error.cause (обратите внимание, что теперь доступны оба стека вызовов):
Error: Something went wrong
at ...
Caused by: SyntaxError: Unexpected token b in JSON at position 2
at JSON.parse (<anonymous>)
at ...
Теперь исходная ошибка сохраняется, а на верхнем уровне отображается понятное сообщение.
❯ Как это выглядит на практике
function fetchUserData() {
try {
JSON.parse('{ broken: true }'); // ← Здесь возникнет ошибка
} catch (parseError) {
throw new Error('Failed to fetch user data', { cause: parseError });
}
}
try {
fetchUserData();
} catch (err) {
console.error(err.message); // "Failed to fetch user data"
console.error(err.cause); // [SyntaxError: Unexpected token b in JSON]
console.error(err.cause instanceof SyntaxError); // true
}
Получается довольно удобно.
Свойство cause по спецификации не является перечисляемым (enumerable) при передаче через конструктор Error, поэтому оно не попадает в логи и циклы for...in, если не обращаться к нему явно. То же самое касается свойств message и stack.
⚠️Примечание: JS не объединяет стеки вызовов автоматически. Стек нового объекта ошибки отображается отдельно. Чтобы получить полный стек вызовов, необходимо вручную обратиться к
err.cause.stack.
❯ До появления cause: сомнительные обходные решения
До введения cause в ES2022 разработчики использовали разные «костыли»: конкатенацию строк, собственные свойства вроде .originalError или полное оборачивание ошибки. Эти методы часто приводили к потере важных данных, таких как исходный стек вызовов или тип ошибки.
Свойство cause решает эту проблему стандартным чистым способом.
❯ Работает и с кастомными ошибками
cause можно использовать и в собственных классах ошибок:
class DatabaseError extends Error {
constructor(message, { cause } = {}) {
super(message, { cause });
this.name = 'DatabaseError';
}
}
Если используется среда выполнения ES2022 и новее, этого достаточно — super(message, { cause }) обработает все автоматически.
Для TypeScript важно, чтобы в tsconfig.json были правильно настроены следующие параметры:
{
"compilerOptions": {
"target": "es2022",
"lib": ["es2022"]
}
}
Иначе при передаче { cause } в конструктор Error возникнет ошибка типа.
❯ Более точные проверки в тестах
Цепочка ошибок полезна не только во время выполнения, но и при тестировании.
Предположим, сервис выбрасывает UserCreationError, вызванный ValidationError. Вместо проверки только верхнеуровневой ошибки можно определить следующее утверждение:
expect(err.cause).toBeInstanceOf(ValidationError);
Тесты становятся более надежными и понятными.
❯ Подводные камни и рекомендации
По умолчанию console.error(err) выводит только верхнеуровневую ошибку. Цепочка cause не отображается автоматически, поэтому ее нужно логировать вручную:
console.error(err);
console.error('Caused by:', err.cause);
Не нужно увлекаться этим слишком сильно. Если логировать каждую мелкую ошибку, отладка может стать еще более запутанной. Используйте это там, где контекст действительно важен.
❯ Рекурсивное отображение полной цепочки ошибок
Вот небольшой вспомогательный код, который безопасно обходит цепочку ошибок:
function logErrorChain(err, level = 0) {
if (!err) return;
console.error(' '.repeat(level * 2) + `${err.name}: ${err.message}`);
if (err.cause instanceof Error) {
logErrorChain(err.cause, level + 1);
} else if (err.cause) {
console.error(' '.repeat((level + 1) * 2) + String(err.cause));
}
}
А вот код для вывода полного стека вызовов:
function logFullErrorChain(err) {
let current = err;
while (current) {
console.error(current.stack);
current = current.cause instanceof Error ? current.cause : null;
}
}
Отлично подходит для сложных систем, где на разных уровнях может возникать множество ошибок.
❯ Цепочка ошибок по уровням
Представим такой сценарий:
Обращение к базе данных завершилось ошибкой
ConnectionTimeoutError.Она была перехвачена и повторно выброшена как
DatabaseError.Эта ошибка снова была перехвачена и обернута в
ServiceUnavailableError.
class ConnectionTimeoutError extends Error {}
class DatabaseError extends Error {}
class ServiceUnavailableError extends Error {}
try {
try {
try {
throw new ConnectionTimeoutError('DB connection timed out');
} catch (networkErr) {
throw new DatabaseError('Failed to connect to database', { cause: networkErr });
}
} catch (dbErr) {
throw new ServiceUnavailableError('Unable to save user data', { cause: dbErr });
}
} catch (finalErr) {
logErrorChain(finalErr);
}
Вывод в консоли:
ServiceUnavailableError: Unable to save user data
DatabaseError: Failed to connect to database
ConnectionTimeoutError: DB connection timed out
❯ Поддержка в браузерах и средах выполнения
Параметр .cause поддерживается во всех современных средах:
✅ Chrome 93+, Firefox 91+, Safari 15+, Edge 93+
✅ Node.js 16.9+
✅ Bun и Deno (актуальные версии)
⚠️ Примечание: DevTools могут не показывать
causeавтоматически. Нужно выводить его явно черезconsole.error('Caused by:', err.cause). Если код транспилируется с помощью Babel или TS, эта возможность не полифилится.
? Более современные подходы
Если цель — писать аккуратный асинхронный код,
Array.fromAsync()станет отличным помощником.
Современная цепочка ошибок
✅ Используем
new Error(message, { cause }), чтобы сохранять контекст✅ Работает со встроенными и кастомными классами ошибок
✅ Поддерживается во всех современных средах (браузеры, Node, Deno, Bun)
✅ Улучшает логи, отладку и тестирование
✅ TS: указываем
"target": "es2022"и"lib": ["es2022"]⚠️ Не забываем явно логировать
err.causeили обходить цепочку ошибок вручную
Чистые стеки вызовов. Полный контекст. Лучшая отладка.
Новости, обзоры продуктов и конкурсы от команды Timeweb.Cloud - в нашем Telegram-канале ↩