JavaScript выполняется в одном потоке: пока идёт долгая операция, страница не может ничего делать. Поэтому запросы к серверу асинхронны — код продолжает работать, а результат приходит позже. Синтаксис async/await позволяет писать такой код так, будто он последовательный.
Как это выглядит
async function loadUser(id) {
const res = await fetch('/api/users/' + id);
const user = await res.json();
return user;
}
Слово async перед функцией означает, что она всегда возвращает промис. Слово await внутри неё приостанавливает выполнение до получения результата, не блокируя браузер. Сравните с записью через цепочку методов: логика та же, но читается сверху вниз, без вложенности.
await не останавливает страницу. Он приостанавливает только текущую функцию, а браузер в это время обрабатывает нажатия, анимации и другие задачи. Останавливает всё как раз синхронный код — например, тяжёлый цикл.Обработка ошибок
Главное удобство — обычный try/catch вместо отдельного обработчика:
async function loadUser(id) {
try {
const res = await fetch('/api/users/' + id);
if (!res.ok) throw new Error('HTTP ' + res.status);
return await res.json();
} catch (e) {
console.error('Не удалось загрузить:', e);
return null;
}
}
Обратите внимание на проверку res.ok: функция fetch не считает ошибкой ответы 404 и 500 — она отклоняет промис только при сетевом сбое. Код состояния нужно проверять руками, иначе вы будете разбирать страницу с ошибкой как JSON.
Последовательно или параллельно
Типичная ошибка производительности — ждать запросы по очереди, когда они независимы:
// медленно: 300 мс, если каждый по 100
const a = await getA();
const b = await getB();
const c = await getC();
// быстро: 100 мс
const [a, b, c] = await Promise.all([getA(), getB(), getC()]);
Правило простое: если результат одного запроса нужен для следующего — только последовательно. Если запросы независимы — всегда через Promise.all.
| Метод | Поведение |
|---|---|
| Promise.all | Ждёт все, падает целиком при первой ошибке |
| Promise.allSettled | Ждёт все и возвращает результат каждого, включая ошибки |
| Promise.race | Возвращает первый завершившийся, любой |
| Promise.any | Возвращает первый успешный |
Где await не сработает как ожидается
- Внутри forEach. Этот метод не ждёт промисы: цикл пролетит мгновенно. Используйте обычный
for...of. - В обработчике события без async. Функцию нужно объявить асинхронной, иначе await — синтаксическая ошибка.
- Без обработки отклонения. Асинхронная функция, вызванная без await и без catch, проглотит ошибку молча.
- В конструкторе класса. Он не может быть асинхронным — выносите загрузку в отдельный метод.
Как правильно перебрать список
// последовательно
for (const id of ids) {
const user = await loadUser(id);
console.log(user);
}
// параллельно
const users = await Promise.all(ids.map(loadUser));
Частые ошибки
- Забывают
awaitи получают промис вместо данных — отсюда классическое undefined при обращении к полю. - Ждут независимые запросы по очереди и утраивают время загрузки.
- Не проверяют
res.okи разбирают страницу ошибки как JSON. - Используют await внутри forEach.
- Оставляют асинхронные вызовы без обработки ошибок.
Что запомнить
Async делает функцию возвращающей промис, await ждёт результат, не блокируя браузер. Ошибки ловятся обычным try/catch, но код состояния HTTP нужно проверять вручную. Независимые запросы запускайте через Promise.all, а для перебора с ожиданием берите for...of, а не forEach. Пропущенный await — причина большинства странных undefined.
Коротко: вопросы и ответы
Можно ли использовать await на верхнем уровне? Да, в модулях это разрешено. В обычном скрипте нужно обернуть код в асинхронную функцию.
Async/await быстрее промисов? Это тот же механизм, только другой синтаксис. Разница лишь в читаемости.
Как сделать паузу? Обернуть setTimeout в промис и дождаться его: await new Promise(r => setTimeout(r, 1000)).
Комментарии