Пошук уроків, статей та іншого контенту
Як передбачувано реагувати на помилки в коді — від вбудованих типів Error до власних класів помилок.
Реальні програми стикаються з очікуваними збоями постійно: користувач вводить некоректні дані, мережа недоступна (модуль «Асинхронний JavaScript» уже показував це для fetch), файл не знайдено. Без структурованого способу реагувати на такі ситуації програма або аварійно завершується, або — гірше — продовжує працювати з некоректними даними. try/catch дозволяє явно визначити: «якщо в цьому блоці щось піде не так, ось що робити замість аварійного завершення».
try {
const result = riskyOperation();
console.log(result);
} catch (error) {
console.error("Щось пішло не так:", error.message);
} finally {
console.log("Виконується завжди — і при успіху, і при помилці");
}Це той самий механізм, що ми вже бачили в уроці про async/await — там try/catch ловив відхилені (rejected) Promise. Тут — та сама конструкція, але для звичайного синхронного коду.
Ключове слово throw негайно зупиняє виконання поточної функції й «підіймається» вгору стеком викликів (модуль «JavaScript під капотом»), доки не знайде найближчий catch:
function withdraw(balance, amount) {
if (amount > balance) {
throw new Error("Недостатньо коштів на рахунку");
}
return balance - amount;
}
try {
withdraw(100, 500);
} catch (error) {
console.error(error.message); // "Недостатньо коштів на рахунку"
}Технічно можна кинути будь-яке значення (throw "помилка", throw 404), але ідіоматичний JavaScript завжди кидає об'єкт Error чи його нащадок — лише в них є стандартні поля message і stack (трасування стека виконання, корисне для дебагу).
JavaScript сам кидає різні типи Error залежно від характеру проблеми — розпізнавання типу допомагає зрозуміти причину, не читаючи повідомлення уважно:
TypeError — операція над значенням неправильного типу (наприклад, виклик undefined як функції).
RangeError — числове значення поза допустимим діапазоном (та сама помилка, що й переповнення стека рекурсії з модуля «Функції»).
ReferenceError — звернення до неоголошеної змінної (та сама помилка з тимчасової мертвої зони, модуль «JavaScript під капотом»).
SyntaxError — некоректний синтаксис, зазвичай при JSON.parse() невалідного рядка.
try {
JSON.parse("{невалідний json}");
} catch (error) {
console.log(error instanceof SyntaxError); // true
}Для великих застосунків корисно розрізняти типи власних помилок (помилка валідації проти помилки мережі), а не покладатись лише на текст повідомлення. Модуль «JavaScript під капотом» показував extends для успадкування — той самий синтаксис працює для розширення вбудованого Error:
class ValidationError extends Error {
constructor(message, field) {
super(message); // обов'язково — встановлює this.message
this.name = "ValidationError";
this.field = field;
}
}
function validateAge(age) {
if (age < 0) {
throw new ValidationError("Вік не може бути від'ємним", "age");
}
}
try {
validateAge(-5);
} catch (error) {
if (error instanceof ValidationError) {
console.log(`Помилка в полі "${error.field}": ${error.message}`);
} else {
throw error; // невідома помилка — прокинути далі, а не тихо проковтнути
}
}Забутий виклик super(message) у конструкторі класу, що розширює Error — найпоширеніша помилка: без нього error.message лишиться порожнім, хоча решта класу виглядатиме коректно написаною.
Порожній catch — один із найшкідливіших патернів у будь-якій мові: помилка зникає безслідно, а програма продовжує роботу так, ніби нічого не сталось, що робить майбутній дебаг значно складнішим:
try {
riskyOperation();
} catch (error) {
// порожньо — НІКОЛИ так не робіть, помилка зникає безслідно
}function safeJsonParse(text, fallback) {
try {
return JSON.parse(text);
} catch (error) {
console.warn("Не вдалось розпарсити JSON:", error.message);
return fallback;
}
}
safeJsonParse('{"a": 1}', {}); // { a: 1 }
safeJsonParse("невалідний", {}); // {} — запасне значення замість аварійного завершенняПорожній блок catch, що беззвучно проковтує помилку.
Забутий super(message) у власному класі, що розширює Error.
Обгортання занадто великого блоку коду в один try — важче зрозуміти, яка саме операція могла кинути помилку; обгортайте саме ризиковану частину.
Кидання рядків чи чисел замість об'єктів Error — втрачається stack trace, необхідний для дебагу.
try/catch/finally перехоплює помилки в синхронному коді так само, як у async/await для Promise. throw негайно зупиняє функцію й передає помилку найближчому catch вище по стеку викликів. Вбудовані типи (TypeError, RangeError, ReferenceError, SyntaxError) виникають природно в самій мові; власні класи через extends Error дозволяють розрізняти типи помилок у великому застосунку. Головне правило: ніколи не залишайте catch порожнім — це ховає реальні проблеми, а не вирішує їх.
Спробуйте самостійно
Створіть клас NotFoundError extends Error і функцію findUser(users, id), яка кидає NotFoundError із повідомленням, що включає id, якщо користувача з таким id немає в масиві users.