Обработка ошибок в асинхронном коде
try/catch с await
async function load() {
try {
const data = await fetchData();
return data;
} catch (error) {
console.error('Ошибка:', error);
throw error;
}
}
catch в промисах
fetchData()
.then((data) => process(data))
.catch((error) => console.error(error));
Один catch на всю цепочку.
Ошибка = unknown
В строгом TypeScript catch даёт unknown:
try {
await fetchData();
} catch (error: unknown) {
if (error instanceof Error) {
console.error(error.message);
} else {
console.error('Неизвестная ошибка');
}
}
В JavaScript — просто error.
Типы ошибок
// Ошибка сети
fetch('/api').catch((err) => {
if (err instanceof TypeError) {
console.log('Сеть недоступна');
}
});
// HTTP-ошибка (проверяем вручную)
const res = await fetch('/api');
if (!res.ok) {
throw new Error(`HTTP ${res.status}`);
}
// Отмена
if (err.name === 'AbortError') {
console.log('Запрос отменён');
}
Не глотай ошибки
Плохо:
try {
await save();
} catch (e) {
// тихо
}
Хорошо:
try {
await save();
} catch (e) {
console.error('Не удалось сохранить:', e);
showError('Ошибка сохранения');
throw e; // или обработать
}
finally
async function load() {
showSpinner();
try {
return await fetchData();
} finally {
hideSpinner();
}
}
finally выполнится даже при ошибке.
Множественные операции
const results = await Promise.allSettled([
saveUser(user),
saveProfile(profile),
saveSettings(settings),
]);
const errors = results.filter((r) => r.status === 'rejected');
if (errors.length) {
console.error('Ошибки:', errors.map((e) => e.reason));
}
allSettled не отклоняется — можно обработать каждую ошибку.
Ошибка в одном из Promise.all
try {
const [a, b, c] = await Promise.all([p1, p2, p3]);
} catch (error) {
// упадёт при первой ошибке
// остальные промисы продолжат выполняться
console.error(error);
}
Если нужно знать все ошибки — allSettled.
Unhandled rejection
// Плохо
fetchData(); // никто не ловит ошибку
// В Node.js — предупреждение и падение в новых версиях
process.on('unhandledRejection', (reason) => {
console.error('Unhandled:', reason);
});
// В браузере
window.addEventListener('unhandledrejection', (event) => {
console.error(event.reason);
});
Всегда добавляй catch или try/catch.
Кастомные ошибки
class ApiError extends Error {
constructor(message, status) {
super(message);
this.name = 'ApiError';
this.status = status;
}
}
async function request(url) {
const res = await fetch(url);
if (!res.ok) {
throw new ApiError(`HTTP ${res.status}`, res.status);
}
return res.json();
}
try {
await request('/api');
} catch (error) {
if (error instanceof ApiError && error.status === 404) {
// обработать 404
}
}
Повторные попытки
async function retry(fn, attempts = 3, delay = 1000) {
for (let i = 0; i < attempts; i++) {
try {
return await fn();
} catch (error) {
if (i === attempts - 1) throw error;
await new Promise((r) => setTimeout(r, delay));
}
}
}
const data = await retry(() => fetchData(), 3, 1000);
С экспоненциальной задержкой:
async function retryBackoff(fn, attempts = 3, baseDelay = 1000) {
for (let i = 0; i < attempts; i++) {
try {
return await fn();
} catch (error) {
if (i === attempts - 1) throw error;
const delay = baseDelay * 2 ** i;
await new Promise((r) => setTimeout(r, delay));
}
}
}
Логирование
async function withLogging(fn) {
try {
return await fn();
} catch (error) {
console.error('[Error]', {
message: error.message,
stack: error.stack,
timestamp: new Date().toISOString(),
});
throw error;
}
}
Итоги
try/catchсawait.catchдля промисов- Проверяй
res.okв fetch - Не глотай ошибки
allSettledдля всех результатов- Кастомные ошибки для типов
- Retry с backoff для сети