Пошук уроків, статей та іншого контенту
Навчитеся створювати й викидати помилки за допомогою throw, а також формулювати корисні повідомлення для діагностики.
JavaScript автоматично створює помилки, коли код виконує некоректну операцію. Наприклад, звернення до властивості null спричиняє TypeError.
Але помилки можуть виникати й у ситуаціях, які синтаксично правильні:
користувач передав порожнє ім’я;
число виходить за допустимий діапазон;
об’єкт не містить обов’язкової властивості;
операцію не можна виконати в поточному стані програми.
У таких випадках використовується оператор throw. Він негайно припиняє виконання поточної функції та передає помилку найближчому відповідному блоку catch.
function divide(a, b) {
if (b === 0) {
throw new Error("Ділення на нуль неможливе");
}
return a / b;
}
console.log(divide(10, 2)); // 5
console.log(divide(10, 0)); // помилкаПісля виконання throw рядок return a / b для другого виклику вже не виконується.
throwЗагальний синтаксис:
throw value;У value можна передати будь-яке значення:
throw "Сталася помилка";
throw 404;
throw { code: "INVALID_DATA" };Однак у прикладному коді майже завжди слід викидати об’єкти помилок, створені через Error або його підтипи:
throw new Error("Сталася помилка");Об’єкт Error містить корисні властивості:
name — назва типу помилки;
message — повідомлення;
stack — стек викликів, який допомагає знайти місце виникнення помилки.
const error = new Error("Файл не знайдено");
console.log(error.name); // Error
console.log(error.message); // Файл не знайдено
console.log(error.stack); // стек викликівtry...catchЩоб перехопити помилку, використовуйте конструкцію try...catch:
function getUserName(user) {
if (!user.name) {
throw new Error("Користувач не має імені");
}
return user.name;
}
try {
const name = getUserName({});
console.log(name);
} catch (error) {
console.log("Не вдалося отримати ім’я");
console.log(error.message);
}Якщо код у блоці try викидає помилку, виконання одразу переходить до catch.
У прикладі:
викликається getUserName;
функція виявляє відсутність імені;
виконується throw;
console.log(name) пропускається;
керування переходить до catch.
JavaScript має кілька стандартних конструкторів помилок.
ErrorЗагальна помилка, коли спеціалізований тип не потрібен:
throw new Error("Не вдалося виконати операцію");TypeErrorВикористовується, коли значення має неправильний тип:
function printLength(value) {
if (typeof value !== "string") {
throw new TypeError("Очікувався рядок");
}
console.log(value.length);
}
printLength(42);RangeErrorВикористовується, коли значення виходить за допустимий діапазон:
function setPercentage(value) {
if (value < 0 || value > 100) {
throw new RangeError("Відсоток має бути від 0 до 100");
}
return value;
}ReferenceError і SyntaxErrorЦі помилки зазвичай створюються самим JavaScript-рушієм:
ReferenceError — звернення до змінної, якої не існує;
SyntaxError — некоректний синтаксис коду.
Створювати їх вручну можна, але в прикладних функціях для перевірки аргументів частіше використовують TypeError, RangeError або звичайний Error.
Повідомлення помилки має пояснювати:
що саме пішло не так;
яке значення або поле спричинило проблему;
яке значення очікувалося;
за можливості — як виправити ситуацію.
Недостатньо інформативне повідомлення:
throw new Error("Неправильні дані");Краще:
throw new TypeError(
'Поле "age" має бути цілим числом від 0 до 120'
);Для динамічних значень використовуйте шаблонні рядки:
function createUser(name, age) {
if (typeof name !== "string" || name.trim() === "") {
throw new TypeError('Параметр "name" має бути непорожнім рядком');
}
if (!Number.isInteger(age) || age < 0 || age > 120) {
throw new RangeError(
`Параметр "age" має бути цілим числом від 0 до 120. Отримано: ${age}`
);
}
return { name: name.trim(), age };
}
try {
console.log(createUser("Олена", 30));
console.log(createUser("", 30));
} catch (error) {
console.error(`${error.name}: ${error.message}`);
}Виведення буде приблизно таким:
{ name: 'Олена', age: 30 }
TypeError: Параметр "name" має бути непорожнім рядкомНе додавайте до повідомлення конфіденційні дані: паролі, токени, повні номери карток або іншу приватну інформацію.
throw у функціяхФункція може перевіряти аргументи та повідомляти виклику, що продовжувати виконання не можна:
function calculateDiscount(price, percent) {
if (typeof price !== "number" || !Number.isFinite(price)) {
throw new TypeError("Ціна має бути скінченним числом");
}
if (percent < 0 || percent > 100) {
throw new RangeError("Знижка має бути в діапазоні від 0 до 100");
}
return price * (1 - percent / 100);
}
try {
const result = calculateDiscount(1000, 15);
console.log(result); // 850
} catch (error) {
console.error(error.message);
}Це дозволяє відокремити:
перевірку даних — усередині функції;
реакцію на помилку — у коді, який викликає функцію.
Такий підхід особливо корисний для функцій, які використовуються в кількох місцях програми.
Якщо всередині функції немає try...catch, помилка поширюється до коду, який викликав цю функцію.
function parseAge(value) {
const age = Number(value);
if (!Number.isInteger(age) || age < 0) {
throw new RangeError("Вік має бути невід’ємним цілим числом");
}
return age;
}
function createProfile(input) {
const age = parseAge(input.age);
return {
name: input.name,
age
};
}
try {
const profile = createProfile({
name: "Андрій",
age: "не число"
});
console.log(profile);
} catch (error) {
console.error(`Не вдалося створити профіль: ${error.message}`);
}Якщо помилка виникла в parseAge, вона проходить через createProfile і потрапляє до зовнішнього catch.
Іноді функція повинна виконати локальні дії, наприклад записати інформацію в журнал, але не може повністю обробити помилку. У такому випадку її можна повторно викинути через throw.
function loadSettings() {
try {
const rawSettings = '{"theme":"dark"}';
return JSON.parse(rawSettings);
} catch (error) {
console.error("Помилка під час читання налаштувань");
throw error;
}
}
try {
const settings = loadSettings();
console.log(settings);
} catch (error) {
console.error("Налаштування не завантажено:", error.message);
}Повторне викидання зберігає початковий об’єкт помилки та його стек викликів.
Не слід без потреби замінювати помилку на нову:
catch (error) {
throw new Error("Щось пішло не так");
}Так можна втратити початковий тип і корисний контекст помилки.
Для складніших програм можна створювати власні класи помилок. Це дає змогу розрізняти помилки за типом, а не аналізувати текст повідомлення.
class ValidationError extends Error {
constructor(message, field) {
super(message);
this.name = "ValidationError";
this.field = field;
}
}
function validateEmail(email) {
if (!email.includes("@")) {
throw new ValidationError(
"Електронна адреса має містити символ @",
"email"
);
}
return true;
}
try {
validateEmail("user.example.com");
} catch (error) {
if (error instanceof ValidationError) {
console.error(`Помилка поля "${error.field}": ${error.message}`);
} else {
console.error("Невідома помилка:", error);
}
}У класі-нащадку важливо викликати super(message), щоб правильно ініціалізувати базовий Error.
Перевірка через instanceof надійніша за перевірку назви:
if (error instanceof ValidationError) {
// Обробка помилки валідації
}throw у конструкції try...finallyБлок finally виконується незалежно від того, чи виникла помилка:
function processData() {
console.log("Початок обробки");
try {
throw new Error("Дані пошкоджені");
} finally {
console.log("Очищення тимчасових ресурсів");
}
}
try {
processData();
} catch (error) {
console.error(error.message);
}Результат:
Початок обробки
Очищення тимчасових ресурсів
Дані пошкодженіfinally зручно використовувати для:
закриття ресурсів;
скидання стану;
приховування індикатора завантаження;
завершення блокування або транзакції.
Нижче наведено повний приклад функції, яка створює замовлення та викидає різні типи помилок залежно від проблеми.
class ValidationError extends Error {
constructor(message, field) {
super(message);
this.name = "ValidationError";
this.field = field;
}
}
function createOrder(data) {
if (data === null || typeof data !== "object") {
throw new TypeError("Замовлення має бути об’єктом");
}
if (typeof data.product !== "string" || data.product.trim() === "") {
throw new ValidationError(
'Поле "product" має бути непорожнім рядком',
"product"
);
}
if (!Number.isInteger(data.quantity) || data.quantity <= 0) {
throw new ValidationError(
'Поле "quantity" має бути додатним цілим числом',
"quantity"
);
}
return {
product: data.product.trim(),
quantity: data.quantity
};
}
const inputs = [
{ product: "Клавіатура", quantity: 2 },
{ product: "", quantity: 1 },
{ product: "Миша", quantity: 0 },
null
];
for (const input of inputs) {
try {
const order = createOrder(input);
console.log("Створено замовлення:", order);
} catch (error) {
if (error instanceof ValidationError) {
console.error(
`Помилка в полі "${error.field}": ${error.message}`
);
} else {
console.error(`${error.name}: ${error.message}`);
}
}
}Цей приклад демонструє кілька важливих принципів:
перевірка виконується до основної логіки;
для неправильного типу використовується TypeError;
помилки конкретних полів мають власний тип;
обробник розрізняє очікувані помилки валідації та інші проблеми;
повідомлення містять назву поля та вимоги до значення.
throw в асинхронному кодіУ функції, оголошеній з async, throw перетворює результат функції на відхилену обіцянку.
async function getProduct(id) {
if (!Number.isInteger(id) || id <= 0) {
throw new RangeError("Ідентифікатор має бути додатним цілим числом");
}
return { id, name: "Ноутбук" };
}
async function main() {
try {
const product = await getProduct(0);
console.log(product);
} catch (error) {
console.error(`${error.name}: ${error.message}`);
}
}
main();Для обробки такої помилки використовують await у try...catch або метод .catch():
getProduct(0)
.then((product) => {
console.log(product);
})
.catch((error) => {
console.error(error.message);
});Якщо виклик асинхронної функції не обробити, можна отримати необроблену відхилену обіцянку.
Errorthrow "Помилка";Це працює технічно, але рядок не має властивостей name, stack і стандартної структури помилки.
Краще:
throw new Error("Помилка");throw без newНеправильно:
throw Error("Помилка");Такий запис у більшості середовищ працює, але явний варіант зрозуміліший і відповідає звичному стилю:
throw new Error("Помилка");Крихкий код:
if (error.message === "Користувача не знайдено") {
// Обробка помилки
}Текст може змінитися, локалізуватися або містити додаткові дані. Для різних сценаріїв краще використовувати окремі типи помилок або додаткові властивості.
catchtry {
riskyOperation();
} catch (error) {
}Такий код приховує проблему та ускладнює діагностику. Навіть якщо помилку не потрібно показувати користувачу, її варто обробити свідомо: записати в журнал, повернути запасне значення або повторно викинути.
Неправильно:
try {
parseData();
} catch (error) {
throw new Error("Помилка обробки");
}Так губляться тип, повідомлення та стек початкової помилки. Якщо потрібно додати контекст, можна скористатися причиною помилки:
try {
parseData();
} catch (error) {
throw new Error("Не вдалося обробити дані", { cause: error });
}Властивість cause доступна в сучасному JavaScript. Вона дає змогу зберегти початкову помилку:
try {
parseData();
} catch (error) {
const wrappedError = new Error("Не вдалося обробити дані", {
cause: error
});
console.error(wrappedError.message);
console.error(wrappedError.cause.message);
}throw призначений для виняткових ситуацій, а не для кожної можливої гілки виконання. Якщо відсутність результату є нормальною ситуацією, інколи краще повернути null, undefined або спеціальний результат.
Викидайте помилку тоді, коли виклик порушує умови, необхідні для коректного виконання операції.
throw негайно припиняє виконання поточної функції та викидає значення.
У прикладному коді слід використовувати Error або його підтипи.
TypeError підходить для неправильного типу, а RangeError — для значення поза допустимим діапазоном.
Повідомлення має пояснювати проблему, поле або значення та очікуваний формат.
try...catch дає змогу перехоплювати та обробляти помилки.
Якщо помилка не оброблена на поточному рівні, вона поширюється вгору стеком викликів.
Помилку можна повторно викинути після локального журналювання або очищення ресурсів.
Власні класи помилок допомагають розрізняти сценарії через instanceof.
В асинхронних функціях throw призводить до відхилення поверненої обіцянки.
Не викидайте рядки без потреби та не приховуйте помилки порожнім catch.