Отладка кода в JavaScript: как выявить причину ошибки с помощью Error.cause
Краткое резюме
Свойство cause в JavaScript помогает упростить отладку кода, сохраняя исходную ошибку и её стек вызовов при обработке исключений. Это позволяет более эффективно выявлять первопричины ошибок в сложных структурах кода.
Обработка ошибок в 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 ()
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 не является перечисляемым при передаче через конструктор Error, поэтому оно не попадает в логи и циклы for...in, если не обращаться к нему явно. Это также относится к свойствам message и stack.
Важно отметить, что JavaScript не объединяет стеки вызовов автоматически. Чтобы получить полный стек вызовов, необходимо вручную обратиться к 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 }) обработает всё автоматически.