Асинхронный код — это как готовка ужина: пока варится картошка, вы режете салат. В программировании тоже хочется делать несколько дел сразу, не блокируя всё вокруг. Но колбэки и промисы часто превращают код в лапшу. Async/await — это способ писать асинхронный код так, будто он синхронный. Прочитайте статью, и вы поймёте, как это работает и как не наступить на грабли.
Что такое async/await и зачем он нужен
Async/await — это синтаксический сахар над промисами. Он позволяет писать асинхронный код, который выглядит как обычный последовательный. Вы ставите async перед функцией — и внутри неё можете использовать await перед вызовом любой функции, возвращающей промис. Await как бы говорит: «Подожди, пока это действие не завершится, а потом продолжай». При этом поток выполнения не блокируется — другие задачи могут работать.
Зачем это нужно? Чтобы код было легче читать и поддерживать. Сравните два подхода.
// Было: цепочка промисов
fetch('/user')
.then(response => response.json())
.then(user => fetch(`/posts/${user.id}`))
.then(response => response.json())
.then(posts => console.log(posts))
.catch(error => console.error(error));
// Стало: async/await
async function loadPosts() {
try {
const userResponse = await fetch('/user');
const user = await userResponse.json();
const postsResponse = await fetch(`/posts/${user.id}`);
const posts = await postsResponse.json();
console.log(posts);
} catch (error) {
console.error(error);
}
}
loadPosts();
Второй вариант читается сверху вниз, как обычный рассказ. Никаких вложенных then и непонятных catch. Это и есть главная ценность async/await.
Как это работает под капотом
Когда вы объявляете функцию как async, она всегда возвращает промис. Даже если вы вернёте просто число, оно обернётся в промис. А await приостанавливает выполнение функции до тех пор, пока промис не разрешится (или не отклонится). Важно: приостанавливается только эта функция, а не весь поток JavaScript. Движок может тем временем выполнять другие задачи.
Вот простой пример:
async function getNumber() {
return 42;
}
getNumber().then(value => console.log(value)); // 42
async function waitAndPrint() {
console.log('Начало');
const result = await new Promise(resolve => setTimeout(() => resolve('Привет'), 1000));
console.log(result);
console.log('Конец');
}
waitAndPrint();
// Выведет: Начало, затем через секунду: Привет, Конец
Обратите внимание: код после await не выполняется сразу — он ждёт. Но другие функции в это время работают. Это похоже на то, как вы ставите чайник и идёте чистить картошку. Чайник закипает сам, а вы не стоите над ним.
Типичные ошибки и как их избежать
Даже с async/await можно наступить на грабли. Вот самые частые.
- Забыли await. Функция вернёт промис, а вы будете думать, что это уже результат. Всегда проверяйте, где нужен await.
- Используете await в цикле forEach. forEach не умеет ждать. Используйте for...of или Promise.all.
- Не обрабатываете ошибки. Без try/catch отклонённый промис приведёт к необработанному исключению.
- Последовательные await там, где можно параллельно. Если два запроса независимы, запускайте их одновременно через Promise.all.
Пример параллельного выполнения:
// Плохо: последовательно
async function sequential() {
const a = await fetch('/a');
const b = await fetch('/b');
return [a, b];
}
// Хорошо: параллельно
async function parallel() {
const [a, b] = await Promise.all([fetch('/a'), fetch('/b')]);
return [a, b];
}
Сравнение подходов: колбэки, промисы, async/await
Чтобы понять преимущества, полезно сравнить три стиля. В таблице ниже — краткая характеристика.
| Подход | Читаемость | Обработка ошибок | Гибкость |
|---|---|---|---|
| Колбэки | Низкая (callback hell) | Сложно (нужно передавать ошибку в каждый колбэк) | Средняя |
| Промисы | Средняя (цепочки then) | Удобно (catch в конце) | Высокая |
| Async/await | Высокая (как синхронный) | Удобно (try/catch) | Высокая |
Как видите, async/await выигрывает по читаемости и обработке ошибок, но не отменяет промисы — они работают под капотом.
Практические советы и лайфхаки
Вот несколько приёмов, которые сделают ваш код чище.
- Всегда оборачивайте await в try/catch, если не уверены, что промис не отклонится.
- Используйте async/await в обработчиках событий и роутах — это упрощает логику.
- Для параллельных запросов применяйте Promise.all, а если нужно дождаться всех, даже с ошибками — Promise.allSettled.
- Не делайте функцию async, если внутри нет await — это лишнее.
- Помните, что await можно использовать только внутри async-функции (или на верхнем уровне модуля в современных средах).
Пример с allSettled:
async function loadAll() {
const results = await Promise.allSettled([
fetch('/a'),
fetch('/b'),
fetch('/c')
]);
results.forEach((result, index) => {
if (result.status === 'fulfilled') {
console.log(`Запрос ${index} успешен:`, result.value);
} else {
console.error(`Запрос ${index} упал:`, result.reason);
}
});
}
Что в итоге
Async/await — это не просто модный синтаксис, а реальный инструмент для упрощения асинхронного кода. Он делает код понятнее, облегчает отладку и уменьшает количество ошибок. Главное — не забывать про параллельное выполнение и обработку исключений. Начните использовать async/await в своём проекте, и вы почувствуете разницу. А если что-то пойдёт не так — вспомните про try/catch и Promise.all. Удачи в кодинге!
Студия WNDER



