DanLevy.net

Тест: Продвинутое владение ошибками JS

Ваши исключения действительно исключительные?

Считаешь, что знаешь ошибки JavaScript досконально?

Что возвращает JSON.stringify(error)?

const error = new Error('Oops');
console.log(JSON.stringify(error));

Объекты Error имеют неперечислимые свойства (message, name, stack), поэтому JSON.stringify() возвращает {}. Это распространённый подводный камень при отправке ошибок в ответах API. Используйте JSON.stringify(error, Object.getOwnPropertyNames(error)) или создайте обычный объект вместо него.

Подумайте о перечисляемых свойствах Error.

В чём разница между этими двумя?

const err = new Error('Test');
console.log(err);
console.log(JSON.stringify(err));

console.log(err) показывает ошибку с её сообщением и трассировкой стека, потому что консоль имеет специальную обработку объектов Error. JSON.stringify(err) возвращает '{}', так как свойства Error не перечисляемы. Эта разница сбивает с толку многих разработчиков, отлаживающих API.

Консоль и JSON по-разному отображают объекты.

Каковы результаты этих проверок?

class CustomError extends Error {}
const err = new CustomError('test');
console.log(err instanceof CustomError);
console.log(err instanceof Error);
console.log(err instanceof Object);

Все три возвращают true. CustomError наследует Error, который наследует Object. Оператор instanceof проверяет всю цепочку прототипов, поэтому экземпляр CustomError также является экземпляром Error и Object.

Вспомните цепочку прототипов.

Что происходит с instanceof Error между iframe‑ами?

console.log(iframeError instanceof Error);

У разных контекстов выполнения (например, iframe) свои конструкторы Error, поэтому instanceof Error может вернуть false для ошибки из другого контекста. Object.prototype.toString.call(obj) обычно распознаёт такие ошибки, но результат можно подделать через Symbol.toStringTag. Это удобная проверка, а не гарантия подлинности.

У разных контекстов свои конструкторы Error.

Что происходит, когда вы бросаете строку?

try {
throw "Oops!";
} catch (e) {
console.log(e instanceof Error);
console.log(typeof e);
}

JavaScript позволяет бросать любое значение. Здесь e instanceof Error равно false, а typeof e — "string". Это может сломать код обработки ошибок, который предполагает, что все пойманные исключения являются объектами Error. Всегда бросайте экземпляры Error для лучшей отладки.

В JavaScript можно выбросить любое значение.

Каково значение err.name?

class CustomError extends Error {
constructor(message) {
super(message);
this.name = this.constructor.name;
}
}
const err = new CustomError('test');
console.log(err.name);

err.name равно "CustomError", потому что this.constructor.name возвращает имя класса. Установка this.name = this.constructor.name — распространённый приём, чтобы пользовательские классы ошибок показывали правильное имя в трассировках стека и сообщениях об ошибке.

Чему равно this.constructor.name?

Что будет выведено, если не установить name?

class MyError extends Error {
// No constructor or name setting
}
const err = new MyError('test');
console.log(err.name);

Если явно не задать this.name, ошибка наследует свойство name по умолчанию из класса Error, которое равно "Error". Поэтому в пользовательских классах ошибок всегда следует устанавливать this.name = this.constructor.name в конструкторе.

Каково унаследованное значение name у Error?

Что возвращает wrapper.cause.message?

const original = new Error('Original error');
const wrapper = new Error('Wrapper',
{ cause: original }
);
console.log(wrapper.cause.message);

Error.cause (ES2022) позволяет цепочкой связывать ошибки, сохраняя исходный контекст ошибки. wrapper.cause ссылается на исходную ошибку, поэтому wrapper.cause.message возвращает "Original error". Это полезно для оборачивания ошибок нижнего уровня в контекст более высокого уровня.

cause сохраняет ссылку на исходную ошибку.

Что делает Error.captureStackTrace?

function createError(msg) {
const err = new Error(msg);
Error.captureStackTrace(err, createError);
return err;
}
const error = createError('test');

Error.captureStackTrace (V8/Node.js) удаляет указанную функцию (createError) из трассировки стека, делая функции‑фабрики ошибок невидимыми для конечных пользователей. Это создаёт более чистые трассировки стека, указывающие на место вызова фабрики, а не на саму фабрику.

Это возможность V8 для управления трассировкой.

Каково сообщение об ошибке?

function validate(value) {
if (!value) {
throw new Error(
`Value ${value} is invalid`
);
}
}
try {
validate(undefined);
} catch (e) {
console.log(e.message);
}

Шаблонные строки преобразуют undefined в строку "undefined" при интерполяции. Сообщение об ошибке становится "Value undefined is invalid". Для более чистых сообщений рекомендуется использовать value ?? 'null' или аналогичные проверки перед интерполяцией.

Как шаблонная строка преобразует undefined?

Что отправляется клиенту?

// Express.js route
app.get('/api/data', (req, res) => {
const error = new Error('Database failed');
res.json({ error });
});

res.json() использует JSON.stringify() внутри, поэтому объект Error превращается в {}. Клиент получает {"error":{}}. Чтобы исправить, используйте res.json({ error: error.message }) или res.json({ error: { message: error.message, name: error.name } }).

Вспомните сериализацию Error через JSON.stringify.

Что может принимать Promise.reject()?

Promise.reject('string').catch(e =>
console.log(typeof e)
);
Promise.reject({code: 404}).catch(e =>
console.log(e.code)
);
Promise.reject(42).catch(e =>
console.log(e)
);

Как и throw, Promise.reject() принимает любое значение — строки, объекты, числа и т.д. Это выводит "string", 404 и 42. Всегда проверяйте тип перехваченных значений в цепочках промисов, особенно при работе с кодом сторонних библиотек, который может отклонять не объектами Error.

Отклонение промиса похоже на throw.

Насколько надёжны error.code и error.errno?

const fs = require('fs');
fs.readFile('missing.txt', (err, data) => {
if (err) {
console.log(err.code); // 'ENOENT'
console.log(err.errno); // -2
}
});

Свойства вроде code и errno зависят от среды (в данном случае Node.js) и не входят в стандартный объект Error. Ошибки в браузере этих свойств не имеют. Всегда проверяйте их наличие: if (err.code === 'ENOENT') вместо того, чтобы полагаться на их существование.

Свойства ошибок зависят от среды и типа ошибки.

Что возвращают эти проверки?

const fakeError = {
name: 'Error',
message: 'Fake error',
stack: 'fake stack'
};
console.log(fakeError instanceof Error);
console.log(Object.prototype.toString.call(
fakeError
) === '[object Error]');

Обе проверки дают false: у обычного объекта нет Error.prototype в цепочке прототипов, а Object.prototype.toString.call(fakeError) возвращает [object Object]. Свойства name, message и stack сами по себе не делают объект ошибкой.

Symbol.toStringTag может изменить строковый тег, поэтому проверка через toString не является гарантией подлинности Error.

Одна проверка обходит прототипы, другая читает тег объекта, на который влияет Symbol.toStringTag.

Овладейте искусством обработки ошибок

От подводных камней сериализации до сбоев instanceof в разных контекстах — эти продвинутые темы отделяют новичков от изношенных профессионалов.

Готовы к новым задачам? Загляните в нашу полную коллекцию викторин для дополнительных головоломок по JavaScript, алгоритмам и не только!