Промисы избавили нас от ада колбэков, но длинные цепочки .then().then().then() всё равно бывают трудночитаемы. Async/await — синтаксический сахар над промисами, который позволяет писать асинхронный код так, словно он синхронный.
Сравни: загрузка данных для страницы заказа на Ozon.
С промисами:
getOrder(id)
.then(order => getProduct(order.productId))
.then(product => getReviews(product.id))
.then(reviews => render(product, reviews))
.catch(e => showError(e))С async/await:
async function loadOrderPage(id) {
const order = await getOrder(id)
const product = await getProduct(order.productId)
const reviews = await getReviews(product.id)
render(product, reviews)
}Тот же код, та же логика — но читается как синхронный алгоритм.
Ключевое слово async делает функцию асинхронной. Такая функция всегда возвращает Promise:
async function getStatus() {
return 'OK' // автоматически: Promise.resolve('OK')
}
getStatus().then(console.log) // 'OK'
// Стрелочная async-функция:
const loadData = async (url) => {
// ...
}await приостанавливает выполнение async-функции до разрешения промиса. Остальной код продолжает работать — только эта функция «ждёт»:
async function loadUserProfile(userId) {
const user = await fetchUser(userId) // ждём пользователя
const avatar = await fetchAvatar(user.id) // ждём аватар
return { ...user, avatar }
}Без await ты получишь промис, а не его значение — самая частая ошибка.
async function placeOrder(cartItems, paymentMethod) {
try {
const order = await createOrder(cartItems)
const payment = await processPayment(order.id, paymentMethod)
if (!payment.success) {
throw new Error('Платёж отклонён банком')
}
await sendConfirmationEmail(order.id)
return { orderId: order.id, status: 'confirmed' }
} catch (e) {
console.error('Ошибка оформления заказа:', e.message)
await cancelOrder(order?.id) // откатываем если нужно
throw e // пробрасываем для обработки выше
}
}Последовательные await — это медленно, если запросы независимы:
// Медленно: 200 + 300 + 150 = 650мс
const user = await fetchUser(id) // 200мс
const orders = await fetchOrders(id) // 300мс
const reviews = await fetchReviews(id) // 150мс
// Быстро: max(200, 300, 150) = 300мс
const [user, orders, reviews] = await Promise.all([
fetchUser(id),
fetchOrders(id),
fetchReviews(id),
])Правило: если запросы независимы — используй Promise.all. Если второй запрос зависит от результата первого — последовательный await.
1. Забыли await — получили промис вместо данных:
// Сломано:
async function showUser(id) {
const user = fetchUser(id) // нет await — user это Promise, не объект!
console.log(user.name) // undefined
}
// Исправлено:
async function showUser(id) {
const user = await fetchUser(id)
console.log(user.name) // 'Иван'
}2. await вне async-функции:
// Сломано — SyntaxError:
function loadData() {
const data = await fetch('/api') // SyntaxError: await вне async
}
// Исправлено:
async function loadData() {
const data = await fetch('/api')
}3. Последовательные await для независимых запросов:
// Медленно — ждём каждый по очереди (650мс):
async function loadDashboard(id) {
const stats = await fetchStats(id) // 200мс — ждём
const activity = await fetchActivity(id) // 300мс — ждём снова
const alerts = await fetchAlerts(id) // 150мс — ждём снова
return { stats, activity, alerts }
}
// Быстро — параллельно (300мс):
async function loadDashboard(id) {
const [stats, activity, alerts] = await Promise.all([
fetchStats(id),
fetchActivity(id),
fetchAlerts(id),
])
return { stats, activity, alerts }
}useEffect(() => { async function load() {...} load() }, []) — стандартный паттерн загрузкиasync function getServerSideProps() — серверный рендерингapp.get('/users', async (req, res) => { const users = await db.find() })Загрузка страницы заказа: последовательно vs параллельно, обработка ошибок
const delay = ms => new Promise(r => setTimeout(r, ms))
// Симуляция API интернет-магазина
async function fetchOrder(orderId) {
await delay(100)
if (orderId <= 0) throw new Error('Неверный ID заказа')
return { id: orderId, productId: 42, quantity: 2, status: 'processing' }
}
async function fetchProduct(productId) {
await delay(150)
return { id: productId, name: 'MacBook Pro', price: 180000 }
}
async function fetchUser(userId) {
await delay(80)
return { id: userId, name: 'Иван Петров', email: 'ivan@mail.ru' }
}
async function fetchShippingAddress(userId) {
await delay(60)
return { city: 'Москва', street: 'Ленина 42', zip: '101000' }
}
// Страница заказа: продукт зависит от заказа (последовательно),
// пользователь и адрес независимы (параллельно)
async function loadOrderPage(orderId, userId) {
try {
// 1. Сначала получаем заказ (100мс)
const order = await fetchOrder(orderId)
console.log('Заказ загружен:', order.id)
// 2. Параллельно: продукт (зависит от заказа) + пользователь + адрес
const [product, user, address] = await Promise.all([
fetchProduct(order.productId), // 150мс
fetchUser(userId), // 80мс
fetchShippingAddress(userId), // 60мс
])
// Итого: 100мс (заказ) + 150мс (самый медленный параллельный) = 250мс
console.log('Товар:', product.name, '—', product.price.toLocaleString('ru-RU') + ' ₽')
console.log('Покупатель:', user.name)
console.log('Адрес:', address.city + ', ' + address.street)
const total = product.price * order.quantity
console.log('Итого:', total.toLocaleString('ru-RU') + ' ₽')
return { order, product, user, address, total }
} catch (e) {
console.error('Ошибка загрузки страницы заказа:', e.message)
return null
}
}
loadOrderPage(1001, 7)
// Тест ошибки
loadOrderPage(-1, 7)Промисы избавили нас от ада колбэков, но длинные цепочки .then().then().then() всё равно бывают трудночитаемы. Async/await — синтаксический сахар над промисами, который позволяет писать асинхронный код так, словно он синхронный.
Сравни: загрузка данных для страницы заказа на Ozon.
С промисами:
getOrder(id)
.then(order => getProduct(order.productId))
.then(product => getReviews(product.id))
.then(reviews => render(product, reviews))
.catch(e => showError(e))С async/await:
async function loadOrderPage(id) {
const order = await getOrder(id)
const product = await getProduct(order.productId)
const reviews = await getReviews(product.id)
render(product, reviews)
}Тот же код, та же логика — но читается как синхронный алгоритм.
Ключевое слово async делает функцию асинхронной. Такая функция всегда возвращает Promise:
async function getStatus() {
return 'OK' // автоматически: Promise.resolve('OK')
}
getStatus().then(console.log) // 'OK'
// Стрелочная async-функция:
const loadData = async (url) => {
// ...
}await приостанавливает выполнение async-функции до разрешения промиса. Остальной код продолжает работать — только эта функция «ждёт»:
async function loadUserProfile(userId) {
const user = await fetchUser(userId) // ждём пользователя
const avatar = await fetchAvatar(user.id) // ждём аватар
return { ...user, avatar }
}Без await ты получишь промис, а не его значение — самая частая ошибка.
async function placeOrder(cartItems, paymentMethod) {
try {
const order = await createOrder(cartItems)
const payment = await processPayment(order.id, paymentMethod)
if (!payment.success) {
throw new Error('Платёж отклонён банком')
}
await sendConfirmationEmail(order.id)
return { orderId: order.id, status: 'confirmed' }
} catch (e) {
console.error('Ошибка оформления заказа:', e.message)
await cancelOrder(order?.id) // откатываем если нужно
throw e // пробрасываем для обработки выше
}
}Последовательные await — это медленно, если запросы независимы:
// Медленно: 200 + 300 + 150 = 650мс
const user = await fetchUser(id) // 200мс
const orders = await fetchOrders(id) // 300мс
const reviews = await fetchReviews(id) // 150мс
// Быстро: max(200, 300, 150) = 300мс
const [user, orders, reviews] = await Promise.all([
fetchUser(id),
fetchOrders(id),
fetchReviews(id),
])Правило: если запросы независимы — используй Promise.all. Если второй запрос зависит от результата первого — последовательный await.
1. Забыли await — получили промис вместо данных:
// Сломано:
async function showUser(id) {
const user = fetchUser(id) // нет await — user это Promise, не объект!
console.log(user.name) // undefined
}
// Исправлено:
async function showUser(id) {
const user = await fetchUser(id)
console.log(user.name) // 'Иван'
}2. await вне async-функции:
// Сломано — SyntaxError:
function loadData() {
const data = await fetch('/api') // SyntaxError: await вне async
}
// Исправлено:
async function loadData() {
const data = await fetch('/api')
}3. Последовательные await для независимых запросов:
// Медленно — ждём каждый по очереди (650мс):
async function loadDashboard(id) {
const stats = await fetchStats(id) // 200мс — ждём
const activity = await fetchActivity(id) // 300мс — ждём снова
const alerts = await fetchAlerts(id) // 150мс — ждём снова
return { stats, activity, alerts }
}
// Быстро — параллельно (300мс):
async function loadDashboard(id) {
const [stats, activity, alerts] = await Promise.all([
fetchStats(id),
fetchActivity(id),
fetchAlerts(id),
])
return { stats, activity, alerts }
}useEffect(() => { async function load() {...} load() }, []) — стандартный паттерн загрузкиasync function getServerSideProps() — серверный рендерингapp.get('/users', async (req, res) => { const users = await db.find() })Загрузка страницы заказа: последовательно vs параллельно, обработка ошибок
const delay = ms => new Promise(r => setTimeout(r, ms))
// Симуляция API интернет-магазина
async function fetchOrder(orderId) {
await delay(100)
if (orderId <= 0) throw new Error('Неверный ID заказа')
return { id: orderId, productId: 42, quantity: 2, status: 'processing' }
}
async function fetchProduct(productId) {
await delay(150)
return { id: productId, name: 'MacBook Pro', price: 180000 }
}
async function fetchUser(userId) {
await delay(80)
return { id: userId, name: 'Иван Петров', email: 'ivan@mail.ru' }
}
async function fetchShippingAddress(userId) {
await delay(60)
return { city: 'Москва', street: 'Ленина 42', zip: '101000' }
}
// Страница заказа: продукт зависит от заказа (последовательно),
// пользователь и адрес независимы (параллельно)
async function loadOrderPage(orderId, userId) {
try {
// 1. Сначала получаем заказ (100мс)
const order = await fetchOrder(orderId)
console.log('Заказ загружен:', order.id)
// 2. Параллельно: продукт (зависит от заказа) + пользователь + адрес
const [product, user, address] = await Promise.all([
fetchProduct(order.productId), // 150мс
fetchUser(userId), // 80мс
fetchShippingAddress(userId), // 60мс
])
// Итого: 100мс (заказ) + 150мс (самый медленный параллельный) = 250мс
console.log('Товар:', product.name, '—', product.price.toLocaleString('ru-RU') + ' ₽')
console.log('Покупатель:', user.name)
console.log('Адрес:', address.city + ', ' + address.street)
const total = product.price * order.quantity
console.log('Итого:', total.toLocaleString('ru-RU') + ' ₽')
return { order, product, user, address, total }
} catch (e) {
console.error('Ошибка загрузки страницы заказа:', e.message)
return null
}
}
loadOrderPage(1001, 7)
// Тест ошибки
loadOrderPage(-1, 7)Ты разрабатываешь систему оформления заказа в интернет-магазине. Напиши `async` функцию `checkout(cartId, paymentInfo)`, которая последовательно: 1. `await validateCart(cartId)` — получает корзину с товарами 2. `await calculateTotal(cart)` — считает итоговую сумму 3. `await processPayment(total, paymentInfo)` — проводит оплату 4. Выводит результат каждого шага Вспомогательные функции уже написаны. Обработай ошибки в `try/catch`.
const cart = await validateCart(cartId). Затем const { total, itemCount } = await calculateTotal(cart). Затем const payment = await processPayment(total, paymentInfo). Весь блок в try/catch.