Пошук уроків, статей та іншого контенту
Типізуйте помилки в catch і створюйте надійні перевірки для різних форматів помилок.
catch має тип unknownУ JavaScript можна передати в throw будь-яке значення:
throw new Error("Помилка");
throw "Помилка у вигляді рядка";
throw { code: "AUTH_REQUIRED", message: "Потрібна авторизація" };
throw 404;Тому TypeScript не може гарантувати, що значення в catch є екземпляром Error.
У строгому режимі TypeScript змінна catch має тип unknown:
try {
// Код, який може завершитися помилкою
} catch (error) {
// error має тип unknown
}unknown змушує спочатку перевірити значення, а вже потім звертатися до його властивостей.
try {
throw new Error("Не вдалося завантажити дані");
} catch (error) {
// Помилка компіляції:
// console.log(error.message);
if (error instanceof Error) {
console.log(error.message);
}
}Такий підхід безпечніший за автоматичне припущення, що кожна помилка має властивість message.
unknown замість anyТип any вимикає перевірку типів:
try {
throw "Помилка";
} catch (error: any) {
// TypeScript не повідомить про проблему,
// хоча у рядка немає властивості message.
console.log(error.message);
}Результат буде undefined, а справжня причина помилки може бути прихована.
З unknown потрібно виконати перевірку:
try {
throw "Помилка";
} catch (error: unknown) {
if (typeof error === "string") {
console.log(error);
}
}Явно вказувати unknown у catch необов’язково, якщо проєкт використовує строгі налаштування TypeScript. Але це може зробити намір коду зрозумілішим.
instanceofДля об’єктів, створених за допомогою Error, використовуйте instanceof Error:
function readConfiguration(): void {
throw new Error("Файл конфігурації не знайдено");
}
try {
readConfiguration();
} catch (error: unknown) {
if (error instanceof Error) {
console.error(`Сталася помилка: ${error.message}`);
} else {
console.error("Отримано невідомий формат помилки");
}
}Після перевірки TypeScript дозволяє використовувати властивості Error:
message;
name;
stack.
try {
JSON.parse("некоректний JSON");
} catch (error: unknown) {
if (error instanceof SyntaxError) {
console.error("Помилка синтаксису JSON:", error.message);
} else if (error instanceof Error) {
console.error("Інша помилка:", error.message);
}
}Спочатку перевіряйте більш конкретний тип, наприклад SyntaxError, а потім загальний Error.
Помилка не обов’язково є об’єктом. Якщо код або зовнішня бібліотека може передати рядок, число чи інше примітивне значення, перевірте його тип:
function getErrorMessage(error: unknown): string {
if (error instanceof Error) {
return error.message;
}
if (typeof error === "string") {
return error;
}
if (typeof error === "number") {
return `Код помилки: ${error}`;
}
return "Невідома помилка";
}Тут функція безпечно обробляє кілька можливих форматів і завжди повертає рядок.
API часто повертають помилки у форматі:
{
"code": "USER_NOT_FOUND",
"message": "Користувача не знайдено"
}Самого приведення типу недостатньо:
interface ApiError {
code: string;
message: string;
}
const error = value as ApiError;Приведення as ApiError не перевіряє дані під час виконання. Воно лише повідомляє TypeScript, що розробник очікує такий тип.
Замість цього створіть перевірку:
type ApiError = {
code: string;
message: string;
};
function isApiError(value: unknown): value is ApiError {
if (typeof value !== "object" || value === null) {
return false;
}
const record = value as Record<string, unknown>;
return (
typeof record.code === "string" &&
typeof record.message === "string"
);
}Тип value is ApiError означає, що після успішної перевірки TypeScript сприйматиме value як ApiError.
try {
throw {
code: "USER_NOT_FOUND",
message: "Користувача не знайдено",
};
} catch (error: unknown) {
if (isApiError(error)) {
console.error(`[${error.code}] ${error.message}`);
}
}У застосунку зручно перетворювати різні формати помилок до єдиного формату.
type NormalizedError = {
message: string;
code?: string;
};
function isRecord(value: unknown): value is Record<string, unknown> {
return typeof value === "object" && value !== null;
}
function normalizeError(error: unknown): NormalizedError {
if (error instanceof Error) {
return {
message: error.message,
};
}
if (typeof error === "string") {
return {
message: error,
};
}
if (isRecord(error)) {
const message =
typeof error.message === "string"
? error.message
: "Невідома помилка";
const code =
typeof error.code === "string"
? error.code
: undefined;
return {
message,
code,
};
}
return {
message: "Невідома помилка",
};
}Ця функція безпечно працює з:
екземплярами Error;
рядками;
об’єктами з message;
об’єктами з message і code;
числами, null, undefined та іншими значеннями.
Приклад використання:
function executeTask(type: "standard" | "api" | "string" | "number"): void {
switch (type) {
case "standard":
throw new Error("Не вдалося виконати операцію");
case "api":
throw {
code: "LIMIT_EXCEEDED",
message: "Перевищено ліміт запитів",
};
case "string":
throw "Сервер тимчасово недоступний";
case "number":
throw 503;
}
}
const taskTypes = ["standard", "api", "string", "number"] as const;
for (const taskType of taskTypes) {
try {
executeTask(taskType);
} catch (error: unknown) {
const normalized = normalizeError(error);
const prefix = normalized.code
? `[${normalized.code}] `
: "";
console.error(`${taskType}: ${prefix}${normalized.message}`);
}
}Цей приклад можна скомпілювати та виконати:
tsc error-handling.ts --strict --target ES2020 --module commonjs
node error-handling.jsПід час обробки помилок від API не варто одразу вважати відповідь потрібним типом. Дані приходять під час виконання програми, тому їх треба перевірити:
type ApiError = {
code: string;
message: string;
};
function isApiError(value: unknown): value is ApiError {
if (!isRecord(value)) {
return false;
}
return (
typeof value.code === "string" &&
typeof value.message === "string"
);
}
async function requestData(): Promise<unknown> {
throw {
code: "SERVICE_UNAVAILABLE",
message: "Сервіс тимчасово недоступний",
};
}
async function loadData(): Promise<void> {
try {
await requestData();
} catch (error: unknown) {
if (isApiError(error)) {
console.error(`Помилка API ${error.code}: ${error.message}`);
return;
}
if (error instanceof Error) {
console.error(`Помилка виконання: ${error.message}`);
return;
}
console.error("Отримано непередбачений формат помилки");
}
}
void loadData();Тип unknown у відповіді requestData нагадує, що зовнішні дані не можна вважати надійними без перевірки.
Для помилок, які створює сам застосунок, можна використовувати власний клас:
class ValidationError extends Error {
constructor(
message: string,
public readonly field: string,
) {
super(message);
this.name = "ValidationError";
}
}
function validateUsername(username: string): void {
if (username.length < 3) {
throw new ValidationError(
"Ім’я користувача має містити щонайменше 3 символи",
"username",
);
}
}
try {
validateUsername("ab");
} catch (error: unknown) {
if (error instanceof ValidationError) {
console.error(`Поле ${error.field}: ${error.message}`);
} else if (error instanceof Error) {
console.error(error.message);
} else {
console.error("Невідома помилка");
}
}Власні класи корисні, коли для різних помилок потрібна різна логіка:
помилку валідації можна показати користувачу;
помилку авторизації можна обробити перенаправленням;
внутрішню помилку можна записати в журнал;
невідомі значення не можна безпечно інтерпретувати.
ErrorНебезпечно:
try {
doSomething();
} catch (error) {
console.log((error as Error).message);
}Приведення типу не змінює значення та не перевіряє його структуру.
Безпечніше:
try {
doSomething();
} catch (error: unknown) {
if (error instanceof Error) {
console.log(error.message);
}
}any у catchany приховує помилки типізації та дозволяє звертатися до неіснуючих властивостей. Використовуйте unknown і звужуйте тип перевірками.
Небезпечно:
if (typeof error === "object" && error !== null) {
const message = (error as { message: string }).message;
console.log(message);
}У такого об’єкта message може бути числом, null або взагалі відсутньою.
Безпечніше перевіряти тип кожного поля:
if (isRecord(error) && typeof error.message === "string") {
console.log(error.message);
}Не варто завжди замінювати будь-яку помилку повідомленням на кшталт «Щось пішло не так». Спочатку розрізняйте відомі формати, а невідомі значення обробляйте як резервний випадок.
У блоці catch зручно діяти в такому порядку:
Перевірити специфічні власні типи, наприклад ValidationError.
Перевірити стандартний Error через instanceof.
Перевірити відомий формат об’єкта через type guard.
Обробити рядки або інші очікувані примітиви.
Для всіх інших значень використати безпечний резервний сценарій.
try {
performOperation();
} catch (error: unknown) {
if (error instanceof ValidationError) {
// Обробка помилки валідації
} else if (error instanceof Error) {
// Обробка стандартної помилки
} else if (isApiError(error)) {
// Обробка помилки API
} else {
// Обробка невідомого значення
}
}Такий порядок гарантує, що доступ до властивостей відбувається лише після відповідної перевірки.
Значення, передане через throw, може мати будь-який тип.
Змінна в catch повинна розглядатися як unknown.
Для стандартних помилок використовуйте error instanceof Error.
Для власних форматів створюйте type guards із синтаксисом value is SomeType.
Не покладайтеся на as SomeType для перевірки даних під час виконання.
Нормалізація різних форматів помилок спрощує логування та подальшу обробку.
Уникайте any у catch, щоб не приховувати помилки типізації.