Разбираемся с Async/Await в JavaScript на примерах

Разбираемся с Async/Await в JavaScript на примерах

Callback — это не что-то замысловатое или особенное, а просто функция, вызов которой отложен на неопределённое время. Благодаря асинхронному характеру JavaScript, обратные вызовы нужны были везде, где результат не может быть получен сразу.

Проблемы начинаются, когда нужно выполнить несколько асинхронных операций. Просто представьте себе подобный сценарий:

  • Выполняется запрос в БД на некого пользователя Arfat. Нужно считать его поле profile_img_url и загрузить соответствующее изображение с сервера someServer.ru.
  • После загрузки изображения необходимо его конвертировать, допустим из PNG в JPEG.
  • В случае успешной конвертации нужно отправить письмо на почту пользователя.
  • Это событие нужно занести в файл transformations.log и указать дату.

Обратите внимание на вложенность обратных вызовов и пирамиду из }) в конце. Подобные случаи принято называть Callback Hell или Pyramid of Doom.

Вот основные недостатки:

  • Такой код сложно читать.
  • В таком коде сложно обрабатывать ошибки и одновременно сохранять его «качество».

Для решения этой проблемы в JavaScript были придуманы промисы (англ. promises). Теперь глубокую вложенность коллбэков можно заменить ключевым словом then:

Код стал читаться сверху вниз, а не слева направо, как это было в случае с обратными вызовами. Это плюс к читаемости.

Однако и у промисов есть свои проблемы:

  • Всё ещё нужно работать с кучей .then.
  • Вместо обычного try/catch нужно использовать .catch для обработки всех ошибок.
  • Работа с несколькими промисами в цикле не всегда интуитивно понятна и местами сложна.

Предположим, что у вас есть цикл for, который выводит последовательность чисел от 0 до 10 со случайным интервалом (от 0 до n секунд). Используя промисы нужно изменить цикл так, чтобы числа выводились в строгой последовательности от 0 до 10. К примеру, если вывод нуля занимает 6 секунд, а единицы 2 секунды, то единица должна дождаться вывода нуля и только потом начать свой отсчёт (чтобы соблюдать последовательность).Frontend разработчик (релокация в Таллин)Neotech, Таллин, от 2 000 до 3 000 €tproger.ruВакансии на tproger.ru

Стоит ли говорить, что в решении этой задачи нельзя использовать конструкцию async/await либо .sort функцию? Решение будет в конце.

Async функции

Добавление async-функций в ES2017 (ES8) сделало работу с промисами легче.

  • Важно отметить, что async-функции работают поверх промисов.
  • Эти функции не являются принципиально другими концепциями.
  • Async-функции были задуманы как альтернатива коду, использующему промисы.
  • Используя конструкцию async/await, можно полностью избежать использование цепочек промисов.
  • С помощью async-функций возможно организовать работу с асинхронным кодом в синхронном стиле.

Как видите, знание промисов всё же необходимо для понимания работы async/await.

Синтаксис

Синтаксис состоит из двух ключевых слов: async и await. Первое делает функцию асинхронной. Именно в таких функциях разрешается использование await. Использование await в любом другом случае вызовет ошибку.

Обратите внимание, что async вставляется в начале объявления функции, а в случае стрелочной функции — между знаком = и скобками.

Async-функции могут быть помещены в объект в качестве методов или же просто использоваться в объявлении класса.

Async-функции всегда возвращают промисы

Функция fn возвращает строку 'hello'. Т. к. это асинхронная функция, значение строки обёртывается в промис (с помощью конструктора).

Тело асинхронной функции всегда обёртывается в новый промис

Если возвращаемое значение является примитивом, async-функция возвращает это значение, обёрнутое в промис. Но если возвращаемое значение и есть объект промиса, его решение возвращается в новом промисе.

Что происходит, когда внутри асинхронной функции возникает какая-нибудь ошибка?

Если ошибка не будет обработана, foo() вернёт промис с реджектом. В таком случае вместо Promise.resolve вернётся Promise.reject, содержащий ошибку.

Суть async-функций в том, что что бы вы не возвращали, на выходе вы всегда будете получать промис.

Асинхронные функции приостанавливаются при каждом await выражении

await сказывается на выражениях. Если выражение является промисом, то async-функция будет приостановлена до тех пор, пока промис не выполнится. Если же выражение не является промисом, то оно конвертируется в промис через Promise.resolve и потом завершается.

Как работает fn функция?

  • После вызова fn функции первая строка конвертируется из const a = await 9; в const a = await Promise.resolve(9);.
  • После использования await, выполнение функции приостанавливается, пока a не получит своё значение (в данном случае это 9).
  • delayAndGetRandom(1000) приостанавливает выполнение fn функции, пока не завершится сама (после 1 секунды). Это, фактически, можно назвать остановкой fnфункции на 1 секунду.
  • Также delayAndGetRandom(1000) через resolve возвращает случайное значение, которое присваивается переменной b.
  • Случай с переменной c идентичен случаю переменной a. После этого опять происходит пауза на 1 секунду, но теперь delayAndGetRandom(1000) ничего не возвращает, т. к. этого не требуется.
  • Под конец эти значения считаются по формуле a + b * c. Результат обёртывается в промис с помощью Promise.resolve и возвращается функцией.

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *