Перейти к содержимому

Ошибки в async/await

Обработка ошибок в async-коде имеет несколько нетривиальных нюансов: unhandled rejection, поглощённые ошибки в catch/finally, потеря async stack trace — всё это реальные production-баги, которые проверяют на Senior-собесах.

// 1. Unhandled rejection — разные поведения в Node.js и браузере
const p = Promise.reject(new Error('oops'));
// Без .catch(): Node.js → UnhandledPromiseRejection (crash с --unhandled-rejections=throw)
// Браузер → window.onunhandledrejection event
process.on('unhandledRejection', (reason, promise) => {
console.error('Unhandled:', reason);
process.exit(1); // рекомендуется — undefined state опасен
});
// Частая ошибка: fire-and-forget без .catch()
async function fireAndForget() {
await doSomething(); // если бросит — unhandled rejection!
}
fireAndForget(); // BAD: нет наблюдения
fireAndForget().catch(console.error); // GOOD
// 2. Область действия try/catch: накрывает ВЕСЬ блок
async function safe() {
try {
const data = await riskyIO();
return transformData(data); // ошибка здесь ТОЖЕ попадёт в catch!
// ↑ это может маскировать логические ошибки в transform
} catch (e) {
if (e instanceof NetworkError) return null; // обработали
throw e; // re-throw всё остальное
}
}
// Паттерн "go-style": разделить IO и логику
async function goStyle() {
const [err, data] = await riskyIO()
.then(d => [null, d])
.catch(e => [e, null]);
if (err) { handleNetworkError(err); return; }
return transformData(data); // ошибки отсюда не поглощаются
}
// 3. return vs await в try/finally — критическая разница
async function withFinallyBad() {
try {
return fetchData(); // Promise без await!
// finally выполнится ДО resolve промиса
} finally {
cleanup(); // выполнится когда promise ещё pending
}
}
async function withFinallyGood() {
try {
return await fetchData(); // await → ждём resolve, потом finally
} finally {
await cleanup(); // тоже можно awaited
// ОСТОРОЖНО: return в finally поглотит исходную ошибку!
}
}
// 4. Async stack traces: Node.js 12+ / V8 7.3+ включают по умолчанию
// В production: source maps + Error.captureStackTrace для сохранения стека

Итог: Правильная обработка ошибок в async-коде требует явного наблюдения за каждым промисом, понимания области действия try/catch (накрывает весь блок включая sync-код) и настройки глобального unhandledRejection хандлера.