- Узнавать встроенные типы ошибок (
TypeError,ReferenceError,RangeError,SyntaxError) и их типичные причины - Создавать свои классы ошибок, добавлять
causeи обрабатывать ошибки по типу, в том числе в асинхронном коде - Читать стек вызовов и отлаживать код с точками останова,
debuggerи методами консоли
Пользователь пишет: «Кнопка ничего не делает». На экране никаких признаков, но в консоли красная строка: в Chrome — TypeError: Cannot read properties of undefined (reading 'price'), а под ней имена файлов с числами. Для новичка это шум, а для профессионала — карта, которая ведёт прямо к ошибке. В этом уроке ты научишься читать такую карту и правильно проектировать собственные ошибки.
Встроенные типы ошибок
| Тип | Типичная причина | Пример |
|---|---|---|
TypeError | операция над значением не того типа: вызов не-функции, чтение свойства undefined/null | null.name, (42)() |
ReferenceError | несуществующая переменная (или переменная в TDZ) | console.log(missing) |
RangeError | число вне допустимого диапазона | new Array(-1) |
SyntaxError | код или JSON, который нельзя разобрать | JSON.parse('{bad}') |
const attempts = [
() => null.name,
() => missingVariable,
() => new Array(-1),
() => JSON.parse('{bad json}'),
() => (42)(),
];
for (const attempt of attempts) {
try {
attempt();
} catch (error) {
console.log(error.name, '|', error instanceof Error);
}
}▸ Ожидаемый результат
TypeError | true ReferenceError | true RangeError | true SyntaxError | true TypeError | true
Собственные ошибки и cause
throw может выбросить любое значение, но всегда выбрасывай объект Error — только у него есть стек вызовов. Хорошее правило: лови только те ошибки, которые можешь обработать, а остальные пробрасывай дальше (throw error). Чтобы различать ошибки своей программы, построй иерархию классов, наследующих Error, — тогда через instanceof можно правильно реагировать на каждый вид.
class AppError extends Error {
constructor(message, options) {
super(message, options);
this.name = this.constructor.name;
}
}
class ValidationError extends AppError {}
class NotFoundError extends AppError {}
function findUser(users, id) {
if (!Number.isInteger(id)) throw new ValidationError(`Bad id: ${id}`);
const user = users.find((u) => u.id === id);
if (!user) throw new NotFoundError(`User ${id} not found`);
return user;
}
const users = [{ id: 1, name: 'Aysel' }];
for (const id of [1, 'x', 7]) {
try {
console.log(findUser(users, id).name);
} catch (error) {
if (error instanceof ValidationError) console.log('400', error.message);
else if (error instanceof NotFoundError) console.log('404', error.message);
else throw error;
}
}▸ Ожидаемый результат
Aysel 400 Bad id: x 404 User 7 not found
this.constructor.name автоматически даёт каждому подклассу правильное имя. 400 и 404 — коды статуса HTTP, мы вернёмся к ним в уроке про API.Когда ловишь низкоуровневую ошибку и выбрасываешь более понятную, сохрани исходную в свойстве **cause** (ES2022). Так пользователь видит ясное сообщение, а разработчик не теряет настоящую причину.
function loadSettings(text) {
try {
return JSON.parse(text);
} catch (error) {
throw new Error('Settings file is broken', { cause: error });
}
}
try {
loadSettings('{ theme: dark }');
} catch (error) {
console.log(error.message);
console.log('Caused by:', error.cause.name);
console.log(error.cause instanceof SyntaxError);
}▸ Ожидаемый результат
Settings file is broken Caused by: SyntaxError true
const wait = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
async function saveScore(score) {
await wait(100);
if (score < 0) throw new RangeError('Score cannot be negative');
return 'saved';
}
try {
console.log(await saveScore(10));
console.log(await saveScore(-5));
} catch (error) {
console.log(`${error.name}: ${error.message}`);
}
const result = await saveScore(-1).catch((error) => `fallback (${error.message})`);
console.log(result);▸ Ожидаемый результат
saved RangeError: Score cannot be negative fallback (Score cannot be negative)
Как читать стек вызовов
Стек вызовов (stack trace) показывает цепочку вызовов в момент создания ошибки, самые новые — сверху. Первая строка — имя и сообщение ошибки, следующие строки выглядят как at функция (файл:строка:столбец). В файле cart.js ниже у второго товара нет свойства product. Вот что печатает Node.js (пути к папкам сокращены, внутренние строки Node опущены):
// cart.js
function calculateTotal(items) {
return items.reduce((sum, item) => sum + item.product.price * item.qty, 0);
}
function renderCart(cart) {
return calculateTotal(cart.items);
}
renderCart({ items: [{ product: { price: 2 }, qty: 1 }, { qty: 3 }] });TypeError: Cannot read properties of undefined (reading 'price')
at cart.js:3:57
at Array.reduce (<anonymous>)
at calculateTotal (cart.js:3:16)
at renderCart (cart.js:6:10)
at Object.<anonymous> (cart.js:8:1)Инструменты отладки
- 1Воспроизведи ошибку
Найди точные шаги, которые приводят к ошибке.
- 2Поставь точку останова
В DevTools ›
Sourcesщёлкни по номеру строки или напиши в кодеdebugger;— при открытых DevTools выполнение остановится там. - 3Изучи значения
Смотри переменные в разделе
Scope, наводи курсор на значения, добавляй выражения вWatch. - 4Иди по шагам
Двигайся по строкам, заходи внутрь функции или выходи из неё.
- 5Исправь и проверь
После исправления снова пройди те же шаги.
Кроме log, у консоли есть и другие полезные методы. Запускатель на этой странице показывает только console.log, поэтому запусти пример в консоли браузера или в Node.js. Результат ниже — из Node.js; измеренное время для sort каждый раз разное.
const students = [
{ name: 'Aysel', score: 92 },
{ name: 'Murad', score: 78 },
];
console.table(students);
console.group('Checkout');
console.log('items: 3');
console.warn('coupon expired');
console.groupEnd();
console.count('click');
console.count('click');
console.assert(students.length > 5, 'Expected more than 5 students');
console.time('sort');
students.sort((a, b) => b.score - a.score);
console.timeEnd('sort');┌─────────┬─────────┬───────┐ │ (index) │ name │ score │ ├─────────┼─────────┼───────┤ │ 0 │ 'Aysel' │ 92 │ │ 1 │ 'Murad' │ 78 │ └─────────┴─────────┴───────┘ Checkout items: 3 coupon expired click: 1 click: 2 Assertion failed: Expected more than 5 students sort: 0.027ms
Допиши класс ValidationError (его name должен быть 'ValidationError') и напиши parseAge(text). Если текст — не целое число, выброси ValidationError с сообщением Not a whole number: <text>, если вне диапазона 0–120 — с сообщением Out of range: <age>, иначе верни возраст.
class ValidationError extends Error {
// set this.name in the constructor
}
function parseAge(text) {
// Number(text) must be an integer from 0 to 120
}
for (const input of ['16', 'abc', '130', '42']) {
try {
console.log('Age:', parseAge(input));
} catch (error) {
if (error instanceof ValidationError) {
console.log(`${error.name}: ${error.message}`);
} else {
throw error;
}
}
}▸ Ожидаемый результат
Age: 16 ValidationError: Not a whole number: abc ValidationError: Out of range: 130 Age: 42
Напиши retry(fn, times): она вызывает fn не больше times раз, пока вызов не будет успешным, и после каждой неудачи выводит Retrying after: <сообщение>. Если все попытки неудачны, выброси ошибку с сообщением All <times> attempts failed, которая хранит последнюю ошибку в cause.
function createFlakyTask(failuresBeforeSuccess) {
let calls = 0;
return () => {
calls++;
if (calls <= failuresBeforeSuccess) throw new Error(`Attempt ${calls} failed`);
return `Success on attempt ${calls}`;
};
}
function retry(fn, times) {
// your code here
}
console.log(retry(createFlakyTask(2), 3));
try {
retry(createFlakyTask(5), 3);
} catch (error) {
console.log(error.message, '| cause:', error.cause.message);
}▸ Ожидаемый результат
Retrying after: Attempt 1 failed Retrying after: Attempt 2 failed Success on attempt 3 Retrying after: Attempt 1 failed Retrying after: Attempt 2 failed Retrying after: Attempt 3 failed All 3 attempts failed | cause: Attempt 3 failed
Главное
TypeError— не тот тип илиundefined,ReferenceError— неизвестная переменная,RangeError— значение вне диапазона,SyntaxError— код или JSON, который нельзя разобрать.- Проверяй
error.nameилиinstanceof, а не текст сообщения — сообщения зависят от браузера. - Собственные классы ошибок и
causeделают ошибки осмысленными и сохраняют исходную причину. - Асинхронная ошибка ловится только через
awaitвнутриtryили через.catch(). - Читай стек с первой строки своего файла; вместо догадок используй точки останова,
debuggerи методы консоли.
Проверь себя
Вопросов: 10. Каждый правильный ответ приносит XP.
const user = null; user.length;?