Async/Await: полное руководство по работе с асинхронным кодом
Разбираемся, как работать с асинхронным кодом с помощью async/await. Узнайте, как избежать callback hell, правильно обрабатывать ошибки, оптимизировать параллельное выполнение операций и применять продвинутые паттерны.
Зачем нужна асинхронность
Представьте, что ваше приложение делает запрос к внешнему API, который отвечает 2 секунды. Без асинхронности программа просто зависнет на это время, блокируя выполнение других операций. Асинхронный код позволяет приложению продолжать работу, пока операция выполняется в фоне.

От callbacks к промисам и async/await
Эволюция асинхронного кода в JavaScript прошла несколько этапов:
Callback-функции (устаревший подход):
fetchUser(userId, (error, user) => {
if (error) {
console.error(error);
return;
}
fetchPosts(user.id, (error, posts) => {
if (error) {
console.error(error);
return;
}
// Callback hell начинается здесь
});
});Промисы (улучшение):
fetchUser(userId)
.then(user => fetchPosts(user.id))
.then(posts => console.log(posts))
.catch(error => console.error(error));Async/Await (современный подход):
async function getUserPosts(userId) {
try {
const user = await fetchUser(userId);
const posts = await fetchPosts(user.id);
return posts;
} catch (error) {
console.error(error);
}
}Основы async/await
Ключевое слово async перед функцией означает, что функция всегда возвращает промис. Ключевое слово await заставляет JavaScript дождаться результата промиса перед продолжением выполнения.
async function fetchData() {
const response = await fetch('https://api.example.com/data');
const data = await response.json();
return data;
}Важные правила:
awaitможно использовать только внутриasyncфункцийasyncфункция всегда возвращает промисЕсли функция возвращает значение, оно автоматически оборачивается в
Promise.resolve()
Обработка ошибок
Try-catch блоки — естественный способ обработки ошибок в async/await:
async function processOrder(orderId) {
try {
const order = await fetchOrder(orderId);
const payment = await processPayment(order);
const confirmation = await sendConfirmation(payment);
return confirmation;
} catch (error) {
if (error.code === 'PAYMENT_FAILED') {
await refundOrder(orderId);
}
throw new Error(`Order processing failed: ${error.message}`);
}
}Можно комбинировать с .catch() для более гранулярной обработки:
async function getData() {
const data = await fetchData().catch(error => {
console.error('Fetch failed:', error);
return getDefaultData(); // Возвращаем данные по умолчанию
});
return data;
}
Параллельное выполнение
Одна из частых ошибок — последовательное выполнение независимых операций:
// Плохо: операции выполняются последовательно (6 секунд)
async function loadData() {
const users = await fetchUsers(); // 3 секунды
const posts = await fetchPosts(); // 3 секунды
return { users, posts };
}Используйте Promise.all() для параллельного выполнения:
// Хорошо: операции выполняются параллельно (3 секунды)
async function loadData() {
const [users, posts] = await Promise.all([
fetchUsers(),
fetchPosts()
]);
return { users, posts };
}Promise.allSettled()
Для случаев, когда нужны результаты всех операций:
async function loadAllData() {
const results = await Promise.allSettled([
fetchUsers(),
fetchPosts(),
fetchComments()
]);
results.forEach((result, index) => {
if (result.status === 'fulfilled') {
console.log(`Operation ${index} succeeded:`, result.value);
} else {
console.error(`Operation ${index} failed:`, result.reason);
}
});
}Promise.race()
Для операций с таймаутом:
async function fetchWithTimeout(url, timeout = 5000) {
const timeoutPromise = new Promise((_, reject) =>
setTimeout(() => reject(new Error('Request timeout')), timeout)
);
return Promise.race([
fetch(url),
timeoutPromise
]);
}Async/await в циклах
Будьте осторожны с циклами — они могут создать неожиданное поведение:
// Последовательная обработка
async function processSequentially(items) {
for (const item of items) {
await processItem(item); // Ждем завершения каждой операции
}
}
// Параллельная обработка
async function processInParallel(items) {
await Promise.all(items.map(item => processItem(item)));
}
// Пакетная обработка
async function processBatches(items, batchSize = 5) {
for (let i = 0; i < items.length; i += batchSize) {
const batch = items.slice(i, i + batchSize);
await Promise.all(batch.map(item => processItem(item)));
}
}Распространенные ошибки
1. Забытый await
// Ошибка: функция вернет промис, а не данные
async function getData() {
return fetchData(); // Забыли await
}
// Правильно
async function getData() {
return await fetchData();
}2. Использование await в forEach
// Не работает: forEach не понимает async
items.forEach(async (item) => {
await processItem(item);
});
// Используйте for...of
for (const item of items) {
await processItem(item);
}3. Создание промисов в цикле без контроля
// Создаст тысячи одновременных запросов
const promises = items.map(item => fetchItem(item));
await Promise.all(promises);
// Лучше контролировать параллелизмПродвинутые паттерны
Retry с экспоненциальной задержкой:
async function fetchWithRetry(url, maxRetries = 3) {
for (let i = 0; i < maxRetries; i++) {
try {
return await fetch(url);
} catch (error) {
if (i === maxRetries - 1) throw error;
const delay = Math.pow(2, i) * 1000;
await new Promise(resolve => setTimeout(resolve, delay));
}
}
}Кэширование результатов:
const cache = new Map();
async function fetchWithCache(key, fetcher) {
if (cache.has(key)) {
return cache.get(key);
}
const result = await fetcher();
cache.set(key, result);
return result;
}Debounce для async функций:
function asyncDebounce(func, wait) {
let timeout;
return function(...args) {
return new Promise((resolve) => {
clearTimeout(timeout);
timeout = setTimeout(async () => {
resolve(await func.apply(this, args));
}, wait);
});
};
}Async/await в других языках
Концепция async/await существует не только в JavaScript:
Python:
async def fetch_data():
response = await aiohttp.get('https://api.example.com')
return await response.json()C#:
async Task<string> FetchDataAsync() {
var response = await httpClient.GetAsync("https://api.example.com");
return await response.Content.ReadAsStringAsync();
}Rust:
async fn fetch_data() -> Result<String, Error> {
let response = reqwest::get("https://api.example.com").await?;
Ok(response.text().await?)
}Производительность и best practices
1. Избегайте излишнего await
// Неоптимально
async function process() {
const result = await computeValue();
return result; // Лишний await
}
// Лучше
async function process() {
return computeValue();
}2. Используйте параллелизм где возможно
// Медленно
const user = await getUser();
const posts = await getPosts();
// Быстрее
const [user, posts] = await Promise.all([getUser(), getPosts()]);3. Добавляйте таймауты для внешних запросов
async function safeFetch(url) {
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), 5000);
try {
const response = await fetch(url, { signal: controller.signal });
return response;
} finally {
clearTimeout(timeout);
}
}Заключение
Async/await делает асинхронный код читаемым и понятным, устраняя проблемы callback hell и упрощая обработку ошибок. Ключевые моменты:
Используйте async/await для последовательных операций
Применяйте Promise.all() для параллельного выполнения
Не забывайте обрабатывать ошибки через try-catch
Будьте осторожны с циклами и всегда используйте await где нужно
Контролируйте параллелизм для избежания перегрузки системы
Практика и понимание того, когда операции должны выполняться последовательно, а когда параллельно, помогут вам писать эффективный асинхронный код.
Кодик предлагает структурированные курсы по JavaScript, Python, C, Rust и многим другим языкам, где вы сможете изучить асинхронное программирование от основ до продвинутых паттернов. Интерактивные уроки с практическими заданиями помогут закрепить материал на реальных примерах.
Остались вопросы по async/await или столкнулись со сложной задачей? Присоединяйтесь к нашему Telegram-каналу, где опытные разработчики и активное сообщество всегда готовы помочь разобраться с любой проблемой — от синтаксических тонкостей до архитектурных решений. Задавайте вопросы, делитесь опытом и учитесь вместе с сообществом!