Async/Await паттерны которые убивают производительность
Async/Await сделал асинхронный код красивым. Но красота обманчива - под капотом легко написать код который работает в разы медленнее чем должен.
Разберём самые частые ошибки которые встречаются в реальных проектах.
1. Последовательные await там где можно параллельно
Это самая распространённая ошибка. Каждый await блокирует выполнение следующего запроса:
// ✗ Плохо - последовательно, 650мс
async function loadDashboard(userId: string) {
const user = await fetchUser(userId) // 200мс
const orders = await fetchOrders(userId) // 300мс
const stats = await fetchStats(userId) // 150мс
return { user, orders, stats }
}
// ✓ Хорошо - параллельно, 300мс (максимум из трёх)
async function loadDashboard(userId: string) {
const [user, orders, stats] = await Promise.all([
fetchUser(userId),
fetchOrders(userId),
fetchStats(userId),
])
return { user, orders, stats }
}
Три независимых запроса которые ждут друг друга - это потеря 350мс на каждый вызов. В продакшне это накапливается.
2. Promise.all без обработки ошибок
Promise.all падает целиком если хоть один промис упал. Один сломанный запрос - вся страница пустая:
// ✗ Плохо - один сбой роняет всё
const [user, orders] = await Promise.all([
fetchUser(userId),
fetchOrders(userId), // если упал — user тоже не получим
])
// ✓ Хорошо - каждый результат независим
const results = await Promise.allSettled([
fetchUser(userId),
fetchOrders(userId),
])
const user = results[0].status === 'fulfilled' ? results[0].value : null
const orders = results[1].status === 'fulfilled' ? results[1].value : []
Promise.allSettled даёт результат каждого промиса независимо - fulfilled или rejected. Страница показывает что смогла загрузить.
3. Потерянные ошибки
Вот реальный кейс: API-запрос падал молча - catch блок просто делал console.log, пользователь видел пустую страницу и не понимал что произошло. Клиент обнаружил баг через неделю.
// ✗ Плохо - ошибка съедается
async function getUser(id: string) {
try {
return await fetchUser(id)
} catch (error) {
console.log(error) // ошибка никуда не идёт
}
}
// ✓ Хорошо - ошибка пробрасывается выше
async function getUser(id: string) {
try {
return await fetchUser(id)
} catch (error) {
console.error('[getUser] Ошибка:', error)
throw error // пусть вызывающий код решает что делать
}
}
4. Promise<any> вместо типизированного результата
Это TypeScript-специфичная ошибка которая перечёркивает весь смысл типизации:
// ✗ Плохо - теряем все преимущества TypeScript
async function fetchUser(id: string): Promise<any> {
const res = await fetch(`/api/users/${id}`)
return res.json()
}
// ✓ Хорошо - явный тип результата
interface User {
id: string
name: string
email: string
}
async function fetchUser(id: string): Promise<User> {
const res = await fetch(`/api/users/${id}`)
if (!res.ok) throw new Error(`Ошибка: ${res.status}`)
return res.json() as Promise<User>
}
5. Async функции там где они не нужны
Каждая async функция создаёт Promise объект - это не бесплатно. Исследование показало что неоптимальное использование async/await может давать до 14x замедление на горячих путях:
// ✗ Плохо - async без причины
async function add(a: number, b: number) {
return a + b // зачем Promise если результат синхронный?
}
// ✓ Хорошо - просто синхронная функция
function add(a: number, b: number) {
return a + b
}
Итог
Async/Await не делает код автоматически правильным - он делает его читаемым. Производительность и надёжность зависят от того как ты им пользуешься. Promise.all вместо последовательных await, Promise.allSettled когда ошибка одного запроса не должна рушить всё, явные типы вместо any, пробрасывание ошибок вместо их поглощения.
Эти паттерны не сложные - просто надо знать где искать проблему. ⚡

Комментарии