async/await
Здесь будет встроен видео-разбор темы (5–7 минут, YouTube / RuTube / VK Видео) — тем, кто лучше воспринимает на слух, текст ниже не обязателен.
Промисы никуда не делись — async/await просто даёт им другую запись. Тот же асинхронный код читается сверху вниз, как обычный: с переменными, if, for и привычным try..catch.
Слово async перед функцией делает ровно одну вещь: заворачивает её результат в промис. Вернули строку — снаружи придёт промис со строкой. Бросили ошибку — придёт промис в состоянии «отклонён». Ничего другого async сам по себе не меняет: тело функции по-прежнему выполняется сразу и синхронно, пока не встретит await.
Отсюда простое правило: вызов async-функции нельзя присвоить переменной и сразу использовать как значение. Его либо ждут через await, либо разворачивают через .then() — как любой другой промис.
Вложенного «промиса внутри промиса» не получится: async не оборачивает промис ещё раз, а дожидается его и отдаёт результат наружу.
await ставит на паузу только ту функцию, в которой написан. Движок запоминает, где остановился, отпускает управление наружу и возвращается сюда, когда промис выполнится. Страница при этом живая: кнопки нажимаются, анимации крутятся, остальной код идёт дальше.
Порядок вывода здесь — «старт», «хвост», «5». Строчка после main() выполняется, пока функция ждёт свою задержку. Именно это отличает await от воображаемой «заморозки браузера»: замирает одна функция, а не вкладка.
await живёт внутри async-функции. На верхнем уровне он работает только в модулях (type="module"), в обычном скрипте это синтаксическая ошибка.
Отклонённый промис, который вы ждёте через await, превращается в обычное исключение. Значит, и ловится оно обычным способом — try..catch, тем же самым, что и ошибка в синхронном коде. Отдельный .catch() больше не нужен, а finally работает как всегда.
Большой плюс: один try накрывает сколько угодно последовательных await — не нужно вешать обработчик на каждый шаг. Если же ошибку ловить негде, её просто не перехватывают: она уедет наружу и станет отклонением того промиса, который вернула ваша async-функция.
try..catch ловит только то, что произошло во время выполнения блока. Вызов без await просто создаёт промис и идёт дальше — ошибка случится позже, когда блок уже закончился.
await в цикле выглядит так естественно, что ошибку замечают уже по секундомеру. Каждая итерация честно дожидается предыдущей, и три независимых запроса по 300 мс складываются в 900 мс вместо 300.
Лечится тем, что промисы создают все сразу, а ждут один раз. map запускает операции, Promise.all отдаёт массив результатов в исходном порядке — и общее время равно времени самой медленной операции, а не их сумме.
Последовательность нужна только тогда, когда следующий шаг зависит от предыдущего (получили пользователя — теперь знаем его id) или когда сервер просит не бомбить его параллельными запросами. Во всех остальных случаях await внутри цикла — это потерянное время.
Цепочка .then() прекрасно справляется с одним шагом, но плохо переносит ветвления и промежуточные значения. Каждый результат живёт в своём колбэке, и чтобы использовать данные первого шага на третьем, приходится тащить их через замыкания или возвращать «пакеты» из нескольких значений.
Во втором варианте user — обычная переменная, видимая до конца функции. Дальше можно писать if, for, ранний return — весь привычный синтаксис языка, который в колбэках работал криво.
| Что нужно сделать | Цепочка .then | async/await |
|---|---|---|
| Использовать результат первого шага в третьем | тащить значение через замыкание или лишний .then | обычная переменная const |
| Обработать ошибку | catch в конце цепочки | try..catch вокруг нужного куска |
| Ветвление или цикл посреди асинхронного кода | условия внутри колбэков | обычные if и for |
| Запустить несколько операций разом | Promise.all | тот же Promise.all, но с await |
- async перед функцией заворачивает её результат в промис: вернули значение — снаружи промис со значением, бросили ошибку — отклонённый промис.
- await приостанавливает только свою функцию; страница и остальной код продолжают работать.
- Ошибки отклонённых промисов ловятся обычным try..catch, и один блок накрывает сразу несколько await.
- await в цикле выстраивает независимые операции в очередь — для параллельного запуска создавайте промисы сразу и ждите их через Promise.all.
- По сравнению с цепочками .then появляются обычные переменные, if, for и ранний return — асинхронный код читается как синхронный.
1/3Что вернёт вызов функции async function f() { return 1; }?
2/3Внутри async-функции выполняется строка await загрузка(). Что происходит со страницей?
3/3Три независимых запроса по 1 секунде вызываются через await в цикле for. Сколько займёт цикл?
Переписать цепочку на async/await
Перепишите этот код на async-функцию: результат должен остаться прежним, .then исчезнуть, а .catch превратиться в try..catch.
Шаги зависят друг от друга, поэтому последовательные await здесь уместны. Один try накрывает оба вызова сразу — отдельный обработчик на каждый шаг не нужен.
Ускорить три независимых ожидания
Функция ждёт три независимые операции по 400 мс и тратит больше 1200 мс. Сделайте так, чтобы вывод остался тем же массивом [1, 2, 3], а ожидание сократилось примерно до 400 мс.
Промисы нужно создать все сразу — таймеры стартуют в момент создания, а await остаётся один. Promise.all вернёт результаты в том же порядке, в каком стояли промисы, независимо от того, кто финишировал первым.
Если бы промисы создавались прямо внутри Promise.all через await, выигрыша не было бы: важно, что операции запущены до ожидания.