Антон Ларичев

Введение
Асинхронное программирование — одна из ключевых концепций JavaScript. До появления async/await разработчики использовали колбэки и промисы, которые приводили к сложному и трудночитаемому коду. Синтаксис async/await, введённый в ES2017, позволяет писать асинхронный код так, будто он синхронный.
В этом руководстве мы разберём: как работают async-функции и await, как обрабатывать ошибки, как запускать задачи параллельно и какие ошибки чаще всего допускают разработчики.
Как работает async/await
Async-функция
Ключевое слово async перед функцией делает её асинхронной. Такая функция всегда возвращает промис, даже если внутри возвращается обычное значение.
async function getUser() {
return { name: 'Иван', age: 25 };
}
// Эквивалентно:
function getUser() {
return Promise.resolve({ name: 'Иван', age: 25 });
}
getUser().then(user => console.log(user)); // { name: 'Иван', age: 25 }
Оператор await
await можно использовать только внутри async-функции. Он приостанавливает выполнение функции до тех пор, пока промис не выполнится, и возвращает его результат.
async function fetchUserData() {
// Ждём завершения HTTP-запроса
const response = await fetch('https://api.example.com/users/1');
// Ждём парсинга тела ответа
const user = await response.json();
return user;
}
Без async/await тот же код выглядел бы так:
function fetchUserData() {
return fetch('https://api.example.com/users/1')
.then(response => response.json())
.then(user => user);
}
Async/await — это синтаксический сахар над промисами, а не замена им.
Обработка ошибок
try/catch
Для обработки ошибок в async/await используется стандартная конструкция try/catch.
async function loadUser(id) {
try {
const response = await fetch(`/api/users/${id}`);
if (!response.ok) {
// Выбрасываем ошибку, если сервер вернул не 2xx
throw new Error(`Ошибка HTTP: ${response.status}`);
}
const user = await response.json();
return user;
} catch (error) {
// Перехватываем и сетевые ошибки, и наши собственные
console.error('Не удалось загрузить пользователя:', error.message);
return null;
}
}
Обработка ошибок на уровне вызова
Иногда удобнее обрабатывать ошибку там, где вызывается функция.
async function getPost(id) {
const response = await fetch(`/api/posts/${id}`);
return response.json();
}
async function main() {
try {
const post = await getPost(42);
console.log(post.title);
} catch (error) {
console.error('Ошибка при загрузке поста:', error.message);
}
}
Параллельное выполнение
Promise.all
Одна из частых ошибок — последовательное ожидание независимых задач. Если задачи не зависят друг от друга, их лучше запускать параллельно.
// Медленно: загружаем последовательно
async function loadDashboardSlow() {
const user = await fetchUser(); // 300 мс
const posts = await fetchPosts(); // 400 мс
const comments = await fetchComments(); // 200 мс
// Итого: ~900 мс
return { user, posts, comments };
}
// Быстро: загружаем параллельно
async function loadDashboardFast() {
const [user, posts, comments] = await Promise.all([
fetchUser(),
fetchPosts(),
fetchComments()
]);
// Итого: ~400 мс (максимальное из трёх)
return { user, posts, comments };
}
Promise.allSettled
Если нужно дождаться всех промисов независимо от результата, используйте Promise.allSettled.
async function loadWidgets() {
const results = await Promise.allSettled([
fetchWeather(),
fetchNews(),
fetchAds()
]);
results.forEach((result, index) => {
if (result.status === 'fulfilled') {
console.log(`Виджет ${index} загружен:`, result.value);
} else {
// Один упавший виджет не ломает всю страницу
console.warn(`Виджет ${index} не загружен:`, result.reason);
}
});
}
Async/await в циклах
forEach не работает с async
Array.forEach не ждёт промисов. Используйте for...of вместо него.
const userIds = [1, 2, 3, 4, 5];
// Неверно: forEach не ждёт await, порядок вывода непредсказуем
userIds.forEach(async (id) => {
const user = await fetchUser(id);
console.log(user);
});
// Верно: for...of гарантирует порядок и дожидается каждой итерации
async function processUsers(ids) {
for (const id of ids) {
const user = await fetchUser(id);
console.log(user);
}
}
Параллельная обработка массива
Если порядок не важен, обрабатывайте элементы параллельно через Promise.all с map.
async function loadAllUsers(ids) {
// Запускаем все запросы одновременно
const users = await Promise.all(ids.map(id => fetchUser(id)));
return users;
}
Частые ошибки
1. Забытый await
Пропущенный await — самая распространённая ошибка. Вы работаете с промисом, а не с его значением.
async function example() {
const data = fetchData(); // Ошибка: забыли await
console.log(data.name); // undefined — data это Promise, а не объект
const correctData = await fetchData(); // Правильно
console.log(correctData.name); // 'Иван'
}
2. Последовательное выполнение независимых запросов
Если запросы не зависят друг от друга — всегда запускайте их параллельно через Promise.all, иначе вы теряете время зря.
3. Необработанные ошибки
Async-функция без try/catch бросит необработанный rejected-промис.
// Опасно: ошибка нигде не перехватывается
async function riskyOperation() {
const data = await mightFail();
return data;
}
// Безопасно: оборачиваем в try/catch
async function safeOperation() {
try {
return await mightFail();
} catch (error) {
console.error('Операция не удалась:', error.message);
return null;
}
}
4. Использование async там, где это не нужно
Не делайте функцию async, если она не содержит await. Это создаёт лишний промис без пользы.
// Избыточно
async function add(a, b) {
return a + b; // await здесь нет, async лишний
}
// Правильно
function add(a, b) {
return a + b;
}
Заключение
Async/await значительно упрощает работу с асинхронным кодом в JavaScript. Ключевые выводы:
async-функция всегда возвращает промисawaitприостанавливает выполнение до разрешения промиса- Используйте
try/catchдля обработки ошибок - Для параллельного выполнения независимых задач применяйте
Promise.all - В циклах используйте
for...of, а неforEach - Не забывайте ставить
awaitперед промисами
Освоив эти принципы, вы сможете писать чистый, читаемый и эффективный асинхронный код.






Комментарии
0