async/await

асинхронность≈ 7 минут

Здесь будет встроен видео-разбор темы (5–7 минут, YouTube / RuTube / VK Видео) — тем, кто лучше воспринимает на слух, текст ниже не обязателен.

Промисы никуда не делись — async/await просто даёт им другую запись. Тот же асинхронный код читается сверху вниз, как обычный: с переменными, if, for и привычным try..catch.

Разделы темы:·

Слово async перед функцией делает ровно одну вещь: заворачивает её результат в промис. Вернули строку — снаружи придёт промис со строкой. Бросили ошибку — придёт промис в состоянии «отклонён». Ничего другого async сам по себе не меняет: тело функции по-прежнему выполняется сразу и синхронно, пока не встретит await.

js
Tab добавляет отступ. Чтобы выйти из редактора, нажмите Escape — фокус перейдёт на кнопку запуска.

Отсюда простое правило: вызов async-функции нельзя присвоить переменной и сразу использовать как значение. Его либо ждут через await, либо разворачивают через .then() — как любой другой промис.

Вложенного «промиса внутри промиса» не получится: async не оборачивает промис ещё раз, а дожидается его и отдаёт результат наружу.

js
Tab добавляет отступ. Чтобы выйти из редактора, нажмите Escape — фокус перейдёт на кнопку запуска.

await ставит на паузу только ту функцию, в которой написан. Движок запоминает, где остановился, отпускает управление наружу и возвращается сюда, когда промис выполнится. Страница при этом живая: кнопки нажимаются, анимации крутятся, остальной код идёт дальше.

js
Tab добавляет отступ. Чтобы выйти из редактора, нажмите Escape — фокус перейдёт на кнопку запуска.

Порядок вывода здесь — «старт», «хвост», «5». Строчка после main() выполняется, пока функция ждёт свою задержку. Именно это отличает await от воображаемой «заморозки браузера»: замирает одна функция, а не вкладка.

await живёт внутри async-функции. На верхнем уровне он работает только в модулях (type="module"), в обычном скрипте это синтаксическая ошибка.

Отклонённый промис, который вы ждёте через await, превращается в обычное исключение. Значит, и ловится оно обычным способом — try..catch, тем же самым, что и ошибка в синхронном коде. Отдельный .catch() больше не нужен, а finally работает как всегда.

js
Tab добавляет отступ. Чтобы выйти из редактора, нажмите Escape — фокус перейдёт на кнопку запуска.

Большой плюс: один try накрывает сколько угодно последовательных await — не нужно вешать обработчик на каждый шаг. Если же ошибку ловить негде, её просто не перехватывают: она уедет наружу и станет отклонением того промиса, который вернула ваша async-функция.

try..catch ловит только то, что произошло во время выполнения блока. Вызов без await просто создаёт промис и идёт дальше — ошибка случится позже, когда блок уже закончился.

js
Tab добавляет отступ. Чтобы выйти из редактора, нажмите Escape — фокус перейдёт на кнопку запуска.

await в цикле выглядит так естественно, что ошибку замечают уже по секундомеру. Каждая итерация честно дожидается предыдущей, и три независимых запроса по 300 мс складываются в 900 мс вместо 300.

js
Tab добавляет отступ. Чтобы выйти из редактора, нажмите Escape — фокус перейдёт на кнопку запуска.

Лечится тем, что промисы создают все сразу, а ждут один раз. map запускает операции, Promise.all отдаёт массив результатов в исходном порядке — и общее время равно времени самой медленной операции, а не их сумме.

js
Tab добавляет отступ. Чтобы выйти из редактора, нажмите Escape — фокус перейдёт на кнопку запуска.

Последовательность нужна только тогда, когда следующий шаг зависит от предыдущего (получили пользователя — теперь знаем его id) или когда сервер просит не бомбить его параллельными запросами. Во всех остальных случаях await внутри цикла — это потерянное время.

Цепочка .then() прекрасно справляется с одним шагом, но плохо переносит ветвления и промежуточные значения. Каждый результат живёт в своём колбэке, и чтобы использовать данные первого шага на третьем, приходится тащить их через замыкания или возвращать «пакеты» из нескольких значений.

js
Tab добавляет отступ. Чтобы выйти из редактора, нажмите Escape — фокус перейдёт на кнопку запуска.
js
Tab добавляет отступ. Чтобы выйти из редактора, нажмите Escape — фокус перейдёт на кнопку запуска.

Во втором варианте user — обычная переменная, видимая до конца функции. Дальше можно писать if, for, ранний return — весь привычный синтаксис языка, который в колбэках работал криво.

Что нужно сделатьЦепочка .thenasync/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.

js
Tab добавляет отступ. Чтобы выйти из редактора, нажмите Escape — фокус перейдёт на кнопку запуска.

Шаги зависят друг от друга, поэтому последовательные await здесь уместны. Один try накрывает оба вызова сразу — отдельный обработчик на каждый шаг не нужен.

jsлистинг · не запускается
Листинг только для чтения: этот код не запускается в браузере.

Ускорить три независимых ожидания

Функция ждёт три независимые операции по 400 мс и тратит больше 1200 мс. Сделайте так, чтобы вывод остался тем же массивом [1, 2, 3], а ожидание сократилось примерно до 400 мс.

js
Tab добавляет отступ. Чтобы выйти из редактора, нажмите Escape — фокус перейдёт на кнопку запуска.

Промисы нужно создать все сразу — таймеры стартуют в момент создания, а await остаётся один. Promise.all вернёт результаты в том же порядке, в каком стояли промисы, независимо от того, кто финишировал первым.

jsлистинг · не запускается
Листинг только для чтения: этот код не запускается в браузере.

Если бы промисы создавались прямо внутри Promise.all через await, выигрыша не было бы: важно, что операции запущены до ожидания.