Обработка ошибок в async коде
В асинхронном коде ошибки перехватываются через
.catchу Promise-цепочек или блокtry/catchвнутриasync-функций; необработанные отклонения Promise приводят кUnhandledPromiseRejection.
Зачем нужно
Синхронный try/catch не ловит ошибки в асинхронных операциях — это частый источник «немых» сбоев. Понимание того, как ошибки распространяются через Promise и async/await, критически важно для написания надёжных приложений с корректной обратной связью пользователю и логированием.
Где используется
- Запросы к API (
fetch,axios) - Работа с базами данных, файловой системой (Node.js)
- Цепочки
Promise.all,Promise.allSettled - Middleware в Express/Fastify
.catch в цепочке Promise
fetch('/api/data')
.then(res => {
if (!res.ok) throw new Error(`HTTP ${res.status}`);
return res.json();
})
.then(data => console.log(data))
.catch(err => {
// Ловит ошибки из ВСЕХ .then выше
console.error('Ошибка запроса:', err.message);
})
.finally(() => {
// Выполняется всегда: успех или ошибка
console.log('Запрос завершён');
});
try/catch с async/await
async function loadUser(id) {
try {
const res = await fetch(`/api/users/${id}`);
if (!res.ok) throw new Error(`Пользователь не найден: ${res.status}`);
const user = await res.json();
return user;
} catch (err) {
// Ловит и сетевые ошибки, и throw выше
console.error('Ошибка загрузки:', err.message);
return null;
} finally {
console.log('loadUser завершён');
}
}
Паттерн: go-style обёртка
// Возвращает [error, data] вместо throw
async function to(promise) {
try {
const data = await promise;
return [null, data];
} catch (err) {
return [err, null];
}
}
async function main() {
const [err, user] = await to(fetch('/api/user').then(r => r.json()));
if (err) {
console.error('Ошибка:', err.message);
return;
}
console.log('Пользователь:', user.name);
}
Promise.all и обработка ошибок
// Promise.all — прерывается при первой ошибке
try {
const [users, posts] = await Promise.all([
fetch('/api/users').then(r => r.json()),
fetch('/api/posts').then(r => r.json())
]);
} catch (err) {
// Ловим ошибку любого из запросов
console.error(err);
}
// Promise.allSettled — дожидается всех, даже при ошибках
const results = await Promise.allSettled([
fetch('/api/users').then(r => r.json()),
fetch('/api/posts').then(r => r.json())
]);
results.forEach(result => {
if (result.status === 'fulfilled') {
console.log('Данные:', result.value);
} else {
console.error('Ошибка:', result.reason.message);
}
});
Глобальный обработчик необработанных отклонений
// В браузере
window.addEventListener('unhandledrejection', (event) => {
console.error('Необработанный rejection:', event.reason);
event.preventDefault(); // подавить вывод в консоль браузера
});
// В Node.js
process.on('unhandledRejection', (reason, promise) => {
console.error('Необработанный rejection:', reason);
});
Цепочка ошибок (Error cause)
async function fetchUser(id) {
try {
const res = await fetch(`/api/users/${id}`);
return await res.json();
} catch (err) {
throw new Error(`Не удалось загрузить пользователя ${id}`, { cause: err });
}
}
try {
await fetchUser(42);
} catch (err) {
console.error(err.message); // Не удалось загрузить пользователя 42
console.error(err.cause.message); // Исходная причина (fetch error)
}
Частые ошибки
1. Забытый await — ошибка не поймается
async function bad() {
try {
fetchData; // без await — try/catch не поймает!
} catch (err) {
console.error(err); // никогда не выполнится
}
}
async function good() {
try {
await fetchData; // правильно
} catch (err) {
console.error(err);
}
}
2. Не возвращён Promise из .then
// Ошибка в вложенном .then не поймается внешним .catch
fetch('/api')
.then(res => {
res.json().then(data => { // вложенный промис — антипаттерн
throw new Error('Ошибка'); // не поймается!
});
})
.catch(err => console.error(err)); // не сработает
// Правильно: возвращай промис из .then
fetch('/api')
.then(res => res.json()) // возвращаем Promise
.then(data => { throw new Error('Ошибка'); })
.catch(err => console.error(err)); // сработает
3. Повторный throw без контекста
// Плохо: теряем исходный стек
catch (err) { throw new Error('Что-то пошло не так'); }
// Хорошо: сохраняем причину
catch (err) { throw new Error('Что-то пошло не так', { cause: err }); }
Связанные темы
- Promise
- async-await
- Типы ошибок -- Error, TypeError, ReferenceError
- Пользовательские ошибки
- _MOC Асинхронность