У JavaScript throw може кинути що завгодно - не лише Error, а й рядок, число, об'єкт чи undefined. Сторонні бібліотеки й старий код справді так роблять. Тому TypeScript не може гарантувати, що в catch прийде Error.
З strict (опція useUnknownInCatchVariables) змінна в catch має тип unknown:
try {
await saveOrder(order);
} catch (error) {
console.log(error.message); // помилка: 'error' is of type 'unknown'
}
Звуження перед використанням:
try {
await saveOrder(order);
} catch (error) {
if (error instanceof ValidationError) {
showFieldErrors(error.errors);
} else if (error instanceof Error) {
showToast(error.message);
} else {
showToast('Невідома помилка');
report(error);
}
}
Допоміжна функція для повідомлення:
function errorMessage(error: unknown): string {
if (error instanceof Error) return error.message;
if (typeof error === 'string') return error;
return 'Невідома помилка';
}
Чому не catch (error: any): це повертає стару небезпечну поведінку - error.response.data.message компілюється й падає з TypeError, якщо помилка мережева й response немає. Явна анотація catch (error: Error) не дозволена - TypeScript не може цього гарантувати.
Пастки з instanceof:
- помилки з іншого вікна (iframe) чи іншої копії бібліотеки в збірці не проходять
instanceof- для них перевіряютьnameчи наявність полів; - власні класи помилок мають правильно наслідувати
Error(class HttpError extends Error) і задаватиname.
Помилки з fetch: fetch не кидає винятку на 404 чи 500 - лише на мережеві помилки. Перевірку response.ok і перетворення на власну помилку (HttpError зі статусом) робить ваш API-клієнт - тоді в catch вона розпізнається через instanceof.
Відхилені проміси - пастка: на параметр колбеку .catch((error) => ...) опція useUnknownInCatchVariables не поширюється, і він має тип any. Його варто явно анотувати: .catch((error: unknown) => ...) - а правило лінтера @typescript-eslint/use-unknown-in-catch-callback-variable нагадає про це. З async/await і try/catch проблеми немає.