Ошибки в 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 хандлера.