asyncawait в JavaScript преимущества и подводные камни


Оглавление (нажмите, чтобы открыть):

Как работает async-await в Javascript?

Делает ли функция асинхронной асинхронную функцию?

Я начал использовать async-await вместо цепочки обещаний. Я сделал что-то вроде

При вызове этой функции я смог увидеть, что весь код происходит асинхронно. Но в какой-то более старой статье, которую я читал, мы не можем создать асинхронную функцию в javascript. Так что делает функцию асинхронной делает ее асинхронной.

Делает ли функция асинхронной асинхронную функцию?

Нет, он заставляет его возвращать обещание и приостанавливает функцию до тех пор, пока обещание не разрешится, когда оно достигнет оператора await .

Обещает способ управления асинхронным кодом, но не останавливайте синхронный, блокируя синхронный код или блокируя.

Делает ли функция асинхронной асинхронную функцию?

Да, мы не можем создать асинхронную функцию в JavaScript. Мы можем использовать асинхронную функцию для выполнения нашего кода после асинхронной задачи, но код будет выполняться только в нашем отдельном потоке.

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

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

Делает ли функция асинхронной асинхронную функцию?

Зависит от того, что вы подразумеваете под асинхронным.

Давайте рассмотрим немного другую версию вашего кода:

Здесь, поскольку мы не await первого обещания до тех пор, пока не будет запущено второе обещание, оба могут одновременно ожидать (они могут не ожидать, если first() вернет выполненное обещание достаточно быстро). Эти двое могут делать вещи одновременно. rPromise может завершиться до dPromise или может завершиться после него. Однако они будут делать что-то одновременно, только если то, что они делают, — это что-то (например, ожидание ответа ввода-вывода от веб-службы), которое происходит за пределами самого javascript.

В некоторых других языках/средах мы могли бы ожидать, что здесь будут работать два потока, возможно, на разных ядрах. В javascript есть только один поток, и бит кода, который фактически выполняется в javascript (а не в библиотеке веб-доступа, от которой javascript ожидает ответ, или таймер, который вызывает задержку, или что-то еще), будет только когда-либо быть запущенным в first() или sec() но никогда в обоих одновременно. Если то, что один, если они внутренне await возвращает, в то время как другой имеет дело с тем, что он await , то дальнейшая обработка не произойдет, пока другая функция не будет завершена.

Это asynchronous (два не происходят в фиксированной последовательности, где одно должно происходить раньше другого).

Он не является многопоточным, и фактический код javascript в каждом из них не происходит одновременно, что можно было бы назвать asychronous в некоторых других контекстах.

Но в какой-то более старой статье, которую я читал, мы не можем создать асинхронную функцию в javascript.

Ну, во-первых, до недавнего времени мы не могли даже создать подобный асинхронный код в javascript до недавнего времени, хотя мы могли создавать обещания другими способами.

Во-вторых, хотя он асинхронный, это не то, о чем некоторые люди из других языков и сред считают асинхронным, где асинхронность в основном обеспечивается многопоточностью, которая обладает обеими возможностями, отсутствующими в javascript, но также и подводными камнями, которых также не хватает.

Правильное использование async/await

Из прочитанного про async/await, я понял что эта конструкция должна упростить код, избавить разработчиков от лишних коллбэков. Я запустил такой пример:

В консоли вижу следующее:

Это говорит о том, что main() срабатывает после console.log(‘Ron once said,’) .

Но в чем суть тогда, если один-черт нужно передавать коллбэк в main() чтобы сначала получить результат запроса, а потом что-то с ним делать?

Как добиться того, чтобы в данном примере сначала сработал main() а потом console.log() ?

2 ответа 2

async определяет, что функция является асинхронной и позволяет использовать внутри нее оператор await , который приостанавливает выполнение функции, на время получения результата. Обращаю ваше внимание, что конструкцию await можно использовать не везде, а только внутри асинхронных функций.

До определения async/await подобно поведение достигалось использованием генераторов в комплекте с библиотеками, вроде co . Я уже детально расписывал этот подход в одном из своих ответов.

Теперь несколько слов о вашем конкретном примере. Основная цель, насколько я могу видеть, в псевдо-синхронном выполнении связки:

Казалось бы, функция main определена как асинхронная и этот блок должен выполняться последовательно. Однако вы упускаете один очень важный момент: определение функции асинхронной не делает ее вызов псевдо-синхронным автоматически. Вам нужно в явном виде дождаться результата выполнения main .

Для этого нужно просто обернуть весь ваш код в IIFE-выражение, использующее асинхронную функцию и дождаться результата выполнения main :

Этот пример выведет:

А вот и работающий пример на JSFiddle.

Замечание:


Код выше я написал на чистом JS, без использования библиотеки-полифила asyncawait . Если вы хотите использовать эту библиотеку, то вместо ключевых слов async / await вам нужно в явном виде вызывать функции этой библиотеки.

В качестве альтернативы, я бы предложил использовать Babel, превращающий ES 6/7 код в то, что уже сегодня можно запустить в большинстве браузеров.

[Перевод] Разбираем Async/Await в JavaScript на примерах

Автор статьи разбирает на примерах Async/Await в JavaScript. В целом, Async/Await — удобный способ написания асинхронного кода. До появления этой возможности подобный код писали с использованием коллбэков и промисов. Автор оригинальной статьи раскрывает преимущества Async/Await, разбирая различные примеры.

Напоминаем: для всех читателей «Хабра» — скидка 10 000 рублей при записи на любой курс Skillbox по промокоду «Хабр».

Skillbox рекомендует: Образовательный онлайн-курс «Java-разработчик».

Callback

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

Вот пример асинхронного чтения файла на Node.js:

Проблемы возникают в тот момент, когда требуется выполнить сразу несколько асинхронных операций. Давайте представим себе вот такой сценарий: выполняется запрос в БД пользователя Arfat, нужно считать его поле profile_img_url и загрузить картинку с сервера someserver.com.
После загрузки конвертируем изображение в иной формат, например из PNG в JPEG. Если конвертация прошла успешно, на почту пользователя отправляется письмо. Далее информация о событии заносится в файл transformations.log с указанием даты.

Стоит обратить внимание на наложенность обратных вызовов и большое количество >) в финальной части кода. Это называется Callback Hell или Pyramid of Doom.

Недостатки такого способа очевидны:

  • Этот код сложно читать.
  • В нем также сложно обрабатывать ошибки, что зачастую приводит к ухудшению качества кода.

Для того чтобы решить эту проблему, в JavaScript были добавлены промисы. Они позволяют заменить глубокую вложенность коллбэков словом .then.

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

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

Вот задача, которая покажет значение последнего пункта.

Предположим, что есть цикл for, выводящий последовательность чисел от 0 до 10 со случайным интервалом (0–n секунд). Используя промисы, нужно изменить этот цикл таким образом, чтобы числа выводились в последовательности от 0 до 10. Так, если вывод нуля занимает 6 секунд, а единицы — 2 секунды, сначала должен быть выведен ноль, а потом уже начнется отсчет вывода единицы.

И конечно, для решения этой задачи мы не используем Async/Await либо .sort. Пример решения — в конце.

Async-функции

Добавление async-функций в ES2020 (ES8) упростило задачу работы с промисами. Отмечу, что async-функции работают «поверх» промисов. Эти функции не представляют собой качественно другие концепции. Async-функции задумывались как альтернатива коду, который использует промисы.
Async/Await дает возможность организовать работу с асинхронным кодом в синхронном стиле.

Таким образом, знание промисов облегчает понимание принципов Async/Await.

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

Async вставляется в самом начале объявления функции, а в случае использования стрелочной функции — между знаком «=» и скобками.

Эти функции можно поместить в объект в качестве методов либо же использовать в объявлении класса.

NB! Стоит помнить, что конструкторы класса и геттеры/сеттеры не могут быть асинхронными.

Семантика и правила выполнения

Async-функции, в принципе, похожи на стандартные JS-функции, но есть и исключения.

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

В частности, fn возвращает строку hello. Ну а поскольку это асинхронная функция, то значение строки обертывается в промис при помощи конструктора.

Вот альтернативная конструкция без Async:

В этом случае возвращение промиса производится «вручную». Асинхронная функция всегда обертывается в новый промис.

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

Но что произойдет в том случае, если внутри асинхронной функции окажется ошибка?


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

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

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

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

А вот описание того, как работает fn-функция.

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

Эти паузы могут напоминать генераторы в ES6, но этому есть свои причины.

Решаем задачу

Ну а теперь давайте рассмотрим решение задачи, которая была указана выше.

В функции finishMyTask используется Await для ожидания результатов таких операций, как queryDatabase, sendEmail, logTaskInFile и других. Если же сравнивать это решение с тем, где использовались промисы, станет очевидным сходство. Тем не менее версия с Async/Await довольно сильно упрощает все синтаксические сложности. В этом случае нет большого количества коллбэков и цепочек вроде .then/.catch.

Вот решение с выводом чисел, здесь есть два варианта.

А вот решение с использованием async-функций.

Необработанные ошибки обертываются в rejected промис. Тем не менее в async-функциях можно использовать конструкцию try/catch для того, чтобы выполнить синхронную обработку ошибок.

canRejectOrReturn() — это асинхронная функция, которая либо удачно выполняется (“perfect number”), либо неудачно завершается с ошибкой (“Sorry, number too big”).

Поскольку в примере выше ожидается выполнение canRejectOrReturn, то собственное неудачное завершение повлечет за собой исполнение блока catch. В результате функция foo завершится либо с undefined (когда в блоке try ничего не возвращается), либо с error caught. В итоге у этой функции не будет неудачного завершения, поскольку try/catch займется обработкой самой функции foo.

Стоит уделить внимание тому, что в примере из foo возвращается canRejectOrReturn. Foo в этом случае завершается либо perfect number, либо возвращается ошибка Error (“Sorry, number too big”). Блок catch никогда не будет исполняться.

Проблема в том, что foo возвращает промис, переданный от
canRejectOrReturn. Поэтому решение функции foo становится решением для canRejectOrReturn. В этом случае код будет состоять всего из двух строк:

А вот что будет, если использовать вместе await и return:

В коде выше foo удачно завершится как с perfect number, так и с error caught. Здесь отказов не будет. Но foo завершится с canRejectOrReturn, а не с undefined. Давайте убедимся в этом, убрав строку return await canRejectOrReturn():

Распространенные ошибки и подводные камни

В некоторых случаях использование Async/Await может приводить к ошибкам.

Такое случается достаточно часто — перед промисом забывается ключевое слово await:

Мастер Йода рекомендует:  Добро пожаловать в WordPress и как это работает

В коде, как видно, нет ни await, ни return. Поэтому foo всегда завершается с undefined без задержки в 1 секунду. Но промис будет выполняться. Если же он выдает ошибку или реджект, то в этом случае будет вызываться UnhandledPromiseRejectionWarning.

Async-функции в обратных вызовах

Async-функции довольно часто используются в .map или .filter в качестве коллбэков. В качестве примера можно привести функцию fetchPublicReposCount(username), которая возвращает количество открытых на GitHub репозиториев. Допустим, есть три пользователя, чьи показатели нам нужны. Вот код для этой задачи:

Нам нужны аккаунты ArfatSalman, octocat, norvig. В этом случае выполняем:

Стоит обратить внимание на Await в обратном вызове .map. Здесь counts — массив промисов, ну а .map — анонимный обратный вызов для каждого указанного пользователя.

Чрезмерно последовательное использование await

В качестве примера возьмем такой код:

Здесь в переменную count помещается число репо, затем это число добавляется в массив counts. Проблема кода в том, что пока с сервера не придут данные первого пользователя, все последующие пользователи будут находиться в режиме ожидания. Таким образом, в единый момент обрабатывается лишь один пользователь.

Если, например, на обработку одного пользователя уходит около 300 мс, то для всех пользователей это уже секунда, затрачиваемое время линейно зависит от числа пользователей. Но раз получение количества репо не зависит друг от друга, процессы можно распараллелить. Для этого нужна работа с .map и Promise.all:

Promise.all на входе получает массив промисов с возвращением промиса. Последний после завершения всех промисов в массиве или при первом реджекте завершается. Может случиться так, что все они не запустятся одновременно, — для того чтобы обеспечить одновременный запуск, можно использовать p-map.

Заключение

Async-функции становятся все более важными для разработки. Ну а для адаптивного использования async-функций стоит воспользоваться Async Iterators. JavaScript-разработчик должен хорошо разбираться в этом.


Как использовать async/await в Node.JS и другие best practices?

Решил начать изучение Node.JS с ES6 и TypeScript.
Написал статичный веб-сервер согласно одному скринкасту, приправив его стрелочными функциями и промисами, TypeScript пока не использую. Хочу использовать async/await, но не понимаю как эта конструкция связана с промисами и предполагаю, что в Node.JS уже есть функции для удобной работы с async/await, не знаю куда копать.
Подскажите, как внедрить async/await и другие технологии для улучшения читабельности и качества кода в текущий код.
Правильно ли я вообще делаю приложение?

Для тех, кто не любит нажимать на ссылки, код в спойлере:

Async/await в JavaScript: преимущества и подводные камни

268 просмотра

4 ответа

75 Репутация автора

Делает ли функция асинхронной асинхронную функцию?

Я начал использовать async-await вместо цепочки обещаний. Я сделал что-то вроде

При вызове этой функции я смог увидеть, что весь код происходит асинхронно. Но в какой-то более старой статье, которую я читал, мы не можем создать асинхронную функцию в javascript. Так что делает функцию асинхронной делает ее асинхронной.

Ответы (4)

2 плюса

682315 Репутация автора

Делает ли функция асинхронной асинхронную функцию?

Нет, он заставляет его возвращать обещание и приостанавливает функцию до тех пор, пока обещание не разрешится, когда оно выполнит await оператор.

Обещает способ управления асинхронным кодом, но не останавливайте синхронный, блокируя синхронный код или блокируя.

Автор: Quentin Размещён: 11.12.2020 09:42

2 плюса

1889 Репутация автора

Делает ли функция асинхронной асинхронную функцию?

Да, мы не можем создать асинхронную функцию в JavaScript. Мы можем использовать асинхронную функцию для выполнения нашего кода после асинхронной задачи, но код будет выполняться только в нашем отдельном потоке.

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

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

Автор: NAVIN Размещён: 11.12.2020 09:52

плюса

93977 Репутация автора

Делает ли функция асинхронной асинхронную функцию?

Зависит от того, что вы подразумеваете под асинхронным .

Давайте рассмотрим немного другую версию вашего кода:

Здесь, поскольку мы не await выполняем первое обещание до тех пор, пока не будет запущено второе обещание, оба могут быть ожидаемы в одно и то же время (они могут и не быть, если first() вернули выполненное обещание достаточно быстро). Эти двое могут делать вещи одновременно. rPromise может завершиться до dPromise или может завершиться после него. Однако они будут делать что-то одновременно, только если то, что они делают, это что-то (например, ожидание ответа ввода-вывода от веб-службы), которое происходит вне самого javascript.

В некоторых других языках / средах мы могли бы ожидать, что здесь будут работать два потока, возможно, на разных ядрах. В javascript есть только один поток, и бит кода, который фактически выполняется в javascript (а не в библиотеке веб-доступа, от которой javascript ожидает ответ, или таймер, который вызывает задержку, или что-то еще), будет только когда-либо быть в first() или одновременно, sec() но никогда в обоих. Если то, что один, если они внутренне await возвращают, возвращает, в то время как другой имеет дело с тем, что он await редактировал, то дальнейшая обработка не произойдет, пока другая функция не будет завершена.

Это так asynchronous (два не происходят в фиксированной последовательности, где одно должно происходить раньше другого).

Он не является многопоточным, и фактический код javascript в каждом из них не происходит в одно и то же время, что можно было бы назвать asychronous в некоторых других контекстах.

Но в какой-то более старой статье, которую я читал, мы не можем создать асинхронную функцию в javascript.

Ну, во-первых, до недавнего времени мы не могли даже создать подобный асинхронный код в javascript до недавнего времени, хотя мы могли создавать обещания другими способами.

Во-вторых, несмотря на то, что он асинхронный, это не то, что некоторые люди из других языков и сред считают асинхронным, где асинхронность в основном обеспечивается многопоточностью, которая имеет обе возможности, отсутствующие в javascript, но также и подводные камни, которых также не хватает.


плюса

6206 Репутация автора

Async и Await — простой способ написания обещаний JavaScript. Но под прикрытием JavaScript преобразует код, чтобы сделать то, что он делал до появления Async и Await.

Под капотом пример вашего кода:

действительно становится этот код:

Или более подробный пример:

Как вы можете видеть, ваш пример кода намного проще для чтения. Но это может сбивать с толку, потому что выглядит синхронно.

[Перевод] JavaScript ES8 и переход на async / await

Недавно мы опубликовали материал «Промисы в ES6: паттерны и анти-паттерны». Он вызвал серьёзный интерес аудитории, в комментариях к нему наши читатели рассуждали об особенностях написания асинхронного кода в современных JS-проектах. Кстати, советуем почитать их комментарии — найдёте там много интересного.

По совету пользователя ilnuribat мы добавили к материалу опрос, целью которого было выяснить популярность промисов, коллбэков и конструкций async / await. По состоянию на 9-е сентября промисы и async / await получили примерно по 43% голосов, с небольшим перевесом async / await, коллбэкам досталось 14%. Главный вывод, который можно сделать, проанализировав результаты опроса и комментарии, заключается в том, что важны все имеющиеся технологии, однако, всё больше программистов тяготеют к async / await. Поэтому сегодня мы решили опубликовать перевод статьи про переход на async / await, которая является продолжением материала о промисах.

Коллбэки, промисы, async / await

На прошлой неделе я писал о промисах, возможности JS, которая появилась в ES6. Промисы были отличным способом вырваться из ада коллбэков. Однако сейчас, когда в Node.js (с версии 7.6.) появилась поддержка async / await , у меня сложилось восприятие промисов как чего-то вроде временного подручного средства. Надо сказать, что async / await можно пользоваться и в браузерном коде благодаря транспиляторам вроде babel.

Хочу сказать, что в этом материале я буду применять самые свежие возможности JS, в том числе — шаблонные литералы и стрелочные функции. Посмотреть список новшеств ES6 можно здесь.

Почему async / await — это замечательно?

До недавних пор асинхронный код в JavaScript, в лучшем случае, выглядел неуклюжим. Для разработчиков, перешедших в JavaScript с таких языков, как Python, Ruby, Java, да практически с любых других, коллбэки и промисы казались неоправданно усложнёнными конструкциями, которые подвержены ошибкам и совершенно сбивают программиста с толку.

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

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

Вот гипотетический пример синхронной версии:

Тут всё предельно просто — ничего нового для любого, кто писал на JS. В коде выполняются три шага: получить список идентификаторов материалов, загрузить сведения о самом популярном и вывести результат.

Всё это хорошо, но в JavaScript нельзя блокировать цикл событий. Поэтому, если идентификаторы статей и сведения о популярной статье поступают из файла, приходят в виде ответа на сетевой запрос, если их читают из базы данных, или если они попадают в программу в результате выполнения любой ресурсоёмкой операции ввода-вывода, соответствующие команды всегда следует делать асинхронными, используя коллбэки или промисы (именно поэтому вышеприведённый код и не будет работать, на самом деле наш клиент для HackerNewsоснован на промисах).

Вот — та же логика, реализованная на коллбэках (пример, опять же, гипотетический):

Да уж. Теперь фрагменты кода, реализующие необходимый нам функционал, вложены друг в друга и мы должны их выравнивать по горизонтали. Если бы тут было 20 шагов вместо трёх, то для выравнивания последнего понадобилось бы 40 пробелов! И, если понадобится добавить новый шаг где-нибудь в середине, пришлось бы заново выравнивать всё то, что находится ниже него. Это приводит к появлению огромных и бесполезных различий между разными состояниями файла в Git. Кроме того, обратите внимание на то, что мы должны обрабатывать ошибки на каждом шаге всей этой структуры. Сгруппировать набор операций в одном блоке try / catch не получится.

Попробуем теперь сделать то же самое, воспользовавшись промисами:

Так, это уже смотрится получше. Все три шага одинаково выровнены по горизонтали и добавление нового шага где-нибудь посередине не сложнее вставки новой строки. В результате можно сказать, что синтаксис промисов слегка многословен из-за необходимости использовать Promise.resolve() и из-за всех присутствующих здесь конструкций .then() .

Теперь, разобравшись с обычными функциями, коллбэками и промисами, посмотрим как сделать то же самое с помощью конструкции async / await :

Вот это уже гораздо лучше! Выглядит то, что у нас получилось, как синхронный код, за исключением того, что тут используется ключевое слово await . Кроме того, мы поместили код в анонимную функцию, объявленную с ключевым словом async для того, чтобы этот фрагмент кода лучше подходил для дальнейшей работы с ним.

Тут надо сказать, что методы hn.getStories() и hn.getItem() устроены так, что они возвращают промисы. При их выполнении, цикл событий не блокируется. Благодаря async / await , впервые в истории JS, мы смогли писать асинхронный код, используя обычный декларативный синтаксис!

Переход на async / await

Итак, как же приступить к использованию async / await в своих проектах? Если вы уже работаете с промисами, значит вы готовы к переходу на новую технологию. Любая функция, которая возвращает промис, может быть вызвана с использованием ключевого слова await , что приведёт к тому, что она вернёт результат разрешения промиса. Однако, если вы собираетесь переходить на async / await с коллбэков, вам понадобится сначала преобразовать их в промисы.

▍Переход на async / await с промисов

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

Кроме того, блок .catch() надо заменить на стандартные блоки try / catch . Как видите, наконец-то мы можем использовать один и тот же подход для обработки ошибок в синхронном и асинхронном контекстах!

Важно отметить ещё и то, что ключевое слово await нельзя использовать на верхнем уровне модулей. Оно должно использоваться внутри функций, объявленных с ключевым словом async .

▍Переход на async / await с коллбэков

Если в вашем коде всё ещё применяются функции обратного вызова, лучший способ перехода на async / await заключается в предварительном преобразовании коллбэков в промисы. Затем, используя вышеописанную методику, код, использующий промисы, переписывают с использованием async / await . О том, как преобразовывать коллбэки в промисы, можно почитать здесь.

Паттерны и подводные камни


Конечно, новые технологии — это всегда и новые проблемы. Вот несколько полезных шаблонов и типовых ошибок, с которыми вы можете столкнуться, переводя свой код на async / await .

▍Циклы

Ещё с тех времён, когда я только начинал писать на JS, передача функций в качестве аргументов для других функций была одной из моих любимых возможностей. Конечно, коллбэки — это беспорядок, но я, например, предпочитал использовать Array.forEach вместо обычного цикла for :

Однако, при использовании await метод Array.forEach правильно работать не будет, так как он рассчитан на выполнение синхронных операций:

В этом примере forEach запускает кучу одновременных асинхронных обращений к getItem() и немедленно возвращает управление, не ожидая результатов, поэтому первым, что будет выведено на экран, окажется строка «done!».

Мастер Йода рекомендует:  10 рисков для безопасности вашего приложения

Если вам нужно дождаться результатов асинхронных операций, это значит, что понадобится либо обычный цикл for (который будет выполнять операции последовательно), либо конструкция Promise.all (она будет выполнять операции параллельно):

▍Оптимизация

При использовании async / await вам больше не нужно думать о том, что пишете вы асинхронный код. Это прекрасно, но тут кроется и самая опасная ловушка новой технологии. Дело в том, что при таком подходе можно забыть о мелочах, которые способны оказать огромное влияние на производительность.

Рассмотрим пример. Предположим, мы хотим получить сведения о двух пользователях Hacker News и сравнить их карму. Вот обычная реализация:

Код это вполне рабочий, но второй вызов getUser() не будет выполнен до тех пор, пока не завершится первый. Вызовы независимы, их можно выполнить параллельно. Поэтому ниже приведено более удачное решение:

Тут стоит отметить, что прежде чем пользоваться этим методом, стоит удостовериться в том, что желаемого можно достичь за счёт параллельного выполнения команд. Во многих случаях асинхронные операции должны выполняться последовательно.

Итоги

Надеюсь, мне удалось показать вам, какие замечательные новшества внесла конструкция async / await в разработку асинхронного кода на JavaScript. Возможность описывать асинхронные конструкции, используя тот же синтаксис, что и синхронные — это стандарт современного программирования. А то, что теперь та же возможность доступна и в JavaScript — огромный шаг вперёд для всех, кто пишет на этом языке.

Уважаемые читатели! Мы знаем, по результатам опроса из предыдущей публикации, что многие из вас пользуются async / await. Поэтому просим поделиться опытом.

Async/Await: Герой, которого JavaScript заслужил

Введение

Написание асинхронного кода – задача не из легких. Когда дело доходит до JavaScript, мы в значительной мере полагаемся на функции обратного вызова для выполнения асинхронных задач, которые могут быть недостаточно интуитивными. Это создает некоторый барьер входа для новичков в программировании на JavaScript и вызывает частые проблемы у тех, кто уже пользовался языком некоторое время.

В этой статье мы исследуем, как можно использовать предложение для ECMAScript 2020 (ES7) для усовершенствования опыта асинхронного программирования на JavaScript, чтобы сделать наш код более понятным и простым в написании.

Мир сегодняшнего дня

Давайте начнем с рассмотрения попытки асинхронного программирования. Следующий пример использует библиотеку запросов, чтобы сделать http-запрос к the Ron Swanson Quotes API и выведет ответ на консоли. Чтобы это сработало, нужно вставить следующий файл с именем app.js и запустить npm install request для установления зависимости. Если у Вас не установлен Node.js, можете скачать его отсюда.

var request = require( ‘request’ );

request( ‘http://ron-swanson-quotes.herokuapp.com/quotes’ , function (error, response, body) <

var quote = getQuote();

Почему это происходит?

Причина того, что цитата переменной неопределенна, в том, что функция обратной связи не вызывается до тех пор, пока не закончится функция запроса. Но так как функция запроса выполняется асинхронно, JavaScript не ждет завершения. Вместо этого он переходит к следующему оператору, который возвращает неопределенную переменную. Для лучшего объяснения того, как JavaScript работает «под капотом», просмотрите эту интересную беседу Филипа Робертса на JSConf EU.

Синхронный код, как правило, легче понять и написать, потому что все выполняется в том порядке, в котором оно написано. Возвращаемые значения имеют широкое применение и довольно интуитивны на других языках, но, к сожалению, мы не можем использовать их так часто, как мы бы хотели в JavaScript, так как они не работают с его асинхронной природой.

Так зачем идти по пути асинхронного кодирования? Представьте, что сетевые запросы и чтение с диска являются тем, что мы называем операциями ввода/вывода (I/O input/output). В синхронном I/O исполнении программа блокируется и ждет передачи данных для завершения. Если это занимает 60 секунд для того, чтобы обратиться к базе данных для завершения, то программа ждет, ничего не делая, в течение 60 секунд. Однако, во время выполнения асинхронной I/O операции, программа может возобновить нормальное выполнение и иметь дело с результатами I/O операции, когда бы они ни поступили. Поэтому и существуют функции обратного вызова, но с обратными вызовами гораздо труднее работать и понимать при чтении источника приложения.

Утопия

Можем ли мы получить лучшее из обоих миров – асинхронный код, который позволяет нам работать с блокированными операциями, но также простой в чтении и написании, как синхронный. Ответ – да. Благодаря предложению ES7 для асинхронных функций (Async/Await).

Когда функция декларирована как асинхронная, тогда она в состоянии обеспечить выполнение вызываемого кода, пока он ожидает решения promise.

Вы можете заменить код в app.js следующим. Мы также должны установить Babel для запуска. Сделайте это с помощью npm install babel . Babel будет преобразовывать наш код ES7 в более работоспособную современную версию.

var request = require( ‘request’ );

return new Promise( function (resolve, reject) <

request( ‘http://ron-swanson-quotes.herokuapp.com/quotes’ , function (error, response, body) <

async function main() <


var quote = await getQuote();

console.log( ‘Ron once said,’ );

Можно видеть, что мы возвращаем promise, что оборачивает сетевой запрос внутри нашей getQuote функции. Внутри функции обратного вызова, переданной запросу, мы вызываем функцию решения promise с телом результата.

Выполните следующее, чтобы запустить этот пример.

. /node_modules/ .bin/babel-node app.js

Этот код выглядит довольно круто и близок к оригинальной попытке. Он выглядит довольно синхронным, хотя это не так. В случае, если Вы не заметили, « Ron once said» был напечатан первым, несмотря на то, что вызывается после main . Это показывает, что мы не блокируем работу, пока ожидаем завершения запроса сети.

Внесение улучшений

Мы можем и дальше улучшить код, добавляя обработку ошибок с блоком try/catch. Если существует ошибка во время запроса, мы можем вызвать функцию отмены promise, которая будет искать ошибки внутри main . Как возвращаемые значения, блоки try/catch ранее недостаточно использовались, потому как их было трудно правильно использовать с асинхронным кодом.

var request = require( ‘request’ );

return new Promise( function (resolve, reject) <

Как работает async-await в Javascript?

Делает ли функция асинхронной асинхронную функцию?

Я начал использовать async-await вместо цепочки обещаний. Я сделал что-то вроде

При вызове этой функции я смог увидеть, что весь код происходит асинхронно. Но в какой-то более старой статье, которую я читал, мы не можем создать асинхронную функцию в javascript. Так что делает функцию асинхронной делает ее асинхронной.

Делает ли функция асинхронной асинхронную функцию?

Нет, он заставляет его возвращать обещание и приостанавливает функцию до тех пор, пока обещание не разрешится, когда оно достигнет оператора await .

Обещает способ управления асинхронным кодом, но не останавливайте синхронный, блокируя синхронный код или блокируя.

Делает ли функция асинхронной асинхронную функцию?

Да, мы не можем создать асинхронную функцию в JavaScript. Мы можем использовать асинхронную функцию для выполнения нашего кода после асинхронной задачи, но код будет выполняться только в нашем отдельном потоке.

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

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

Делает ли функция асинхронной асинхронную функцию?

Зависит от того, что вы подразумеваете под асинхронным.

Давайте рассмотрим немного другую версию вашего кода:

Здесь, поскольку мы не await первого обещания до тех пор, пока не будет запущено второе обещание, оба могут одновременно ожидать (они могут не ожидать, если first() вернет выполненное обещание достаточно быстро). Эти двое могут делать вещи одновременно. rPromise может завершиться до dPromise или может завершиться после него. Однако они будут делать что-то одновременно, только если то, что они делают, — это что-то (например, ожидание ответа ввода-вывода от веб-службы), которое происходит за пределами самого javascript.

В некоторых других языках/средах мы могли бы ожидать, что здесь будут работать два потока, возможно, на разных ядрах. В javascript есть только один поток, и бит кода, который фактически выполняется в javascript (а не в библиотеке веб-доступа, от которой javascript ожидает ответ, или таймер, который вызывает задержку, или что-то еще), будет только когда-либо быть запущенным в first() или sec() но никогда в обоих одновременно. Если то, что один, если они внутренне await возвращает, в то время как другой имеет дело с тем, что он await , то дальнейшая обработка не произойдет, пока другая функция не будет завершена.

Это asynchronous (два не происходят в фиксированной последовательности, где одно должно происходить раньше другого).

Он не является многопоточным, и фактический код javascript в каждом из них не происходит одновременно, что можно было бы назвать asychronous в некоторых других контекстах.

Но в какой-то более старой статье, которую я читал, мы не можем создать асинхронную функцию в javascript.

Ну, во-первых, до недавнего времени мы не могли даже создать подобный асинхронный код в javascript до недавнего времени, хотя мы могли создавать обещания другими способами.

Во-вторых, хотя он асинхронный, это не то, о чем некоторые люди из других языков и сред считают асинхронным, где асинхронность в основном обеспечивается многопоточностью, которая обладает обеими возможностями, отсутствующими в javascript, но также и подводными камнями, которых также не хватает.

[Перевод] JavaScript ES8 и переход на async / await

Недавно мы опубликовали материал «Промисы в ES6: паттерны и анти-паттерны». Он вызвал серьёзный интерес аудитории, в комментариях к нему наши читатели рассуждали об особенностях написания асинхронного кода в современных JS-проектах. Кстати, советуем почитать их комментарии — найдёте там много интересного.

По совету пользователя ilnuribat мы добавили к материалу опрос, целью которого было выяснить популярность промисов, коллбэков и конструкций async / await. По состоянию на 9-е сентября промисы и async / await получили примерно по 43% голосов, с небольшим перевесом async / await, коллбэкам досталось 14%. Главный вывод, который можно сделать, проанализировав результаты опроса и комментарии, заключается в том, что важны все имеющиеся технологии, однако, всё больше программистов тяготеют к async / await. Поэтому сегодня мы решили опубликовать перевод статьи про переход на async / await, которая является продолжением материала о промисах.

Коллбэки, промисы, async / await

На прошлой неделе я писал о промисах, возможности JS, которая появилась в ES6. Промисы были отличным способом вырваться из ада коллбэков. Однако сейчас, когда в Node.js (с версии 7.6.) появилась поддержка async / await , у меня сложилось восприятие промисов как чего-то вроде временного подручного средства. Надо сказать, что async / await можно пользоваться и в браузерном коде благодаря транспиляторам вроде babel.


Хочу сказать, что в этом материале я буду применять самые свежие возможности JS, в том числе — шаблонные литералы и стрелочные функции. Посмотреть список новшеств ES6 можно здесь.

Почему async / await — это замечательно?

До недавних пор асинхронный код в JavaScript, в лучшем случае, выглядел неуклюжим. Для разработчиков, перешедших в JavaScript с таких языков, как Python, Ruby, Java, да практически с любых других, коллбэки и промисы казались неоправданно усложнёнными конструкциями, которые подвержены ошибкам и совершенно сбивают программиста с толку.

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

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

Вот гипотетический пример синхронной версии:

Тут всё предельно просто — ничего нового для любого, кто писал на JS. В коде выполняются три шага: получить список идентификаторов материалов, загрузить сведения о самом популярном и вывести результат.

Всё это хорошо, но в JavaScript нельзя блокировать цикл событий. Поэтому, если идентификаторы статей и сведения о популярной статье поступают из файла, приходят в виде ответа на сетевой запрос, если их читают из базы данных, или если они попадают в программу в результате выполнения любой ресурсоёмкой операции ввода-вывода, соответствующие команды всегда следует делать асинхронными, используя коллбэки или промисы (именно поэтому вышеприведённый код и не будет работать, на самом деле наш клиент для HackerNewsоснован на промисах).

Вот — та же логика, реализованная на коллбэках (пример, опять же, гипотетический):

Да уж. Теперь фрагменты кода, реализующие необходимый нам функционал, вложены друг в друга и мы должны их выравнивать по горизонтали. Если бы тут было 20 шагов вместо трёх, то для выравнивания последнего понадобилось бы 40 пробелов! И, если понадобится добавить новый шаг где-нибудь в середине, пришлось бы заново выравнивать всё то, что находится ниже него. Это приводит к появлению огромных и бесполезных различий между разными состояниями файла в Git. Кроме того, обратите внимание на то, что мы должны обрабатывать ошибки на каждом шаге всей этой структуры. Сгруппировать набор операций в одном блоке try / catch не получится.

Попробуем теперь сделать то же самое, воспользовавшись промисами:

Так, это уже смотрится получше. Все три шага одинаково выровнены по горизонтали и добавление нового шага где-нибудь посередине не сложнее вставки новой строки. В результате можно сказать, что синтаксис промисов слегка многословен из-за необходимости использовать Promise.resolve() и из-за всех присутствующих здесь конструкций .then() .

Теперь, разобравшись с обычными функциями, коллбэками и промисами, посмотрим как сделать то же самое с помощью конструкции async / await :

Вот это уже гораздо лучше! Выглядит то, что у нас получилось, как синхронный код, за исключением того, что тут используется ключевое слово await . Кроме того, мы поместили код в анонимную функцию, объявленную с ключевым словом async для того, чтобы этот фрагмент кода лучше подходил для дальнейшей работы с ним.

Мастер Йода рекомендует:  Как правильно использовать элементы HTML H1-H6

Тут надо сказать, что методы hn.getStories() и hn.getItem() устроены так, что они возвращают промисы. При их выполнении, цикл событий не блокируется. Благодаря async / await , впервые в истории JS, мы смогли писать асинхронный код, используя обычный декларативный синтаксис!

Переход на async / await

Итак, как же приступить к использованию async / await в своих проектах? Если вы уже работаете с промисами, значит вы готовы к переходу на новую технологию. Любая функция, которая возвращает промис, может быть вызвана с использованием ключевого слова await , что приведёт к тому, что она вернёт результат разрешения промиса. Однако, если вы собираетесь переходить на async / await с коллбэков, вам понадобится сначала преобразовать их в промисы.

▍Переход на async / await с промисов

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

Кроме того, блок .catch() надо заменить на стандартные блоки try / catch . Как видите, наконец-то мы можем использовать один и тот же подход для обработки ошибок в синхронном и асинхронном контекстах!

Важно отметить ещё и то, что ключевое слово await нельзя использовать на верхнем уровне модулей. Оно должно использоваться внутри функций, объявленных с ключевым словом async .

▍Переход на async / await с коллбэков

Если в вашем коде всё ещё применяются функции обратного вызова, лучший способ перехода на async / await заключается в предварительном преобразовании коллбэков в промисы. Затем, используя вышеописанную методику, код, использующий промисы, переписывают с использованием async / await . О том, как преобразовывать коллбэки в промисы, можно почитать здесь.

Паттерны и подводные камни

Конечно, новые технологии — это всегда и новые проблемы. Вот несколько полезных шаблонов и типовых ошибок, с которыми вы можете столкнуться, переводя свой код на async / await .

▍Циклы

Ещё с тех времён, когда я только начинал писать на JS, передача функций в качестве аргументов для других функций была одной из моих любимых возможностей. Конечно, коллбэки — это беспорядок, но я, например, предпочитал использовать Array.forEach вместо обычного цикла for :

Однако, при использовании await метод Array.forEach правильно работать не будет, так как он рассчитан на выполнение синхронных операций:

В этом примере forEach запускает кучу одновременных асинхронных обращений к getItem() и немедленно возвращает управление, не ожидая результатов, поэтому первым, что будет выведено на экран, окажется строка «done!».

Если вам нужно дождаться результатов асинхронных операций, это значит, что понадобится либо обычный цикл for (который будет выполнять операции последовательно), либо конструкция Promise.all (она будет выполнять операции параллельно):

▍Оптимизация

При использовании async / await вам больше не нужно думать о том, что пишете вы асинхронный код. Это прекрасно, но тут кроется и самая опасная ловушка новой технологии. Дело в том, что при таком подходе можно забыть о мелочах, которые способны оказать огромное влияние на производительность.

Рассмотрим пример. Предположим, мы хотим получить сведения о двух пользователях Hacker News и сравнить их карму. Вот обычная реализация:

Код это вполне рабочий, но второй вызов getUser() не будет выполнен до тех пор, пока не завершится первый. Вызовы независимы, их можно выполнить параллельно. Поэтому ниже приведено более удачное решение:

Тут стоит отметить, что прежде чем пользоваться этим методом, стоит удостовериться в том, что желаемого можно достичь за счёт параллельного выполнения команд. Во многих случаях асинхронные операции должны выполняться последовательно.


Итоги

Надеюсь, мне удалось показать вам, какие замечательные новшества внесла конструкция async / await в разработку асинхронного кода на JavaScript. Возможность описывать асинхронные конструкции, используя тот же синтаксис, что и синхронные — это стандарт современного программирования. А то, что теперь та же возможность доступна и в JavaScript — огромный шаг вперёд для всех, кто пишет на этом языке.

Уважаемые читатели! Мы знаем, по результатам опроса из предыдущей публикации, что многие из вас пользуются async / await. Поэтому просим поделиться опытом.

[Перевод] Разбираем Async/Await в JavaScript на примерах

Автор статьи разбирает на примерах Async/Await в JavaScript. В целом, Async/Await — удобный способ написания асинхронного кода. До появления этой возможности подобный код писали с использованием коллбэков и промисов. Автор оригинальной статьи раскрывает преимущества Async/Await, разбирая различные примеры.

Напоминаем: для всех читателей «Хабра» — скидка 10 000 рублей при записи на любой курс Skillbox по промокоду «Хабр».

Skillbox рекомендует: Образовательный онлайн-курс «Java-разработчик».

Callback

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

Вот пример асинхронного чтения файла на Node.js:

Проблемы возникают в тот момент, когда требуется выполнить сразу несколько асинхронных операций. Давайте представим себе вот такой сценарий: выполняется запрос в БД пользователя Arfat, нужно считать его поле profile_img_url и загрузить картинку с сервера someserver.com.
После загрузки конвертируем изображение в иной формат, например из PNG в JPEG. Если конвертация прошла успешно, на почту пользователя отправляется письмо. Далее информация о событии заносится в файл transformations.log с указанием даты.

Стоит обратить внимание на наложенность обратных вызовов и большое количество >) в финальной части кода. Это называется Callback Hell или Pyramid of Doom.

Недостатки такого способа очевидны:

  • Этот код сложно читать.
  • В нем также сложно обрабатывать ошибки, что зачастую приводит к ухудшению качества кода.

Для того чтобы решить эту проблему, в JavaScript были добавлены промисы. Они позволяют заменить глубокую вложенность коллбэков словом .then.

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

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

Вот задача, которая покажет значение последнего пункта.

Предположим, что есть цикл for, выводящий последовательность чисел от 0 до 10 со случайным интервалом (0–n секунд). Используя промисы, нужно изменить этот цикл таким образом, чтобы числа выводились в последовательности от 0 до 10. Так, если вывод нуля занимает 6 секунд, а единицы — 2 секунды, сначала должен быть выведен ноль, а потом уже начнется отсчет вывода единицы.

И конечно, для решения этой задачи мы не используем Async/Await либо .sort. Пример решения — в конце.

Async-функции

Добавление async-функций в ES2020 (ES8) упростило задачу работы с промисами. Отмечу, что async-функции работают «поверх» промисов. Эти функции не представляют собой качественно другие концепции. Async-функции задумывались как альтернатива коду, который использует промисы.
Async/Await дает возможность организовать работу с асинхронным кодом в синхронном стиле.

Таким образом, знание промисов облегчает понимание принципов Async/Await.

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

Async вставляется в самом начале объявления функции, а в случае использования стрелочной функции — между знаком «=» и скобками.

Эти функции можно поместить в объект в качестве методов либо же использовать в объявлении класса.

NB! Стоит помнить, что конструкторы класса и геттеры/сеттеры не могут быть асинхронными.

Семантика и правила выполнения

Async-функции, в принципе, похожи на стандартные JS-функции, но есть и исключения.

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

В частности, fn возвращает строку hello. Ну а поскольку это асинхронная функция, то значение строки обертывается в промис при помощи конструктора.

Вот альтернативная конструкция без Async:

В этом случае возвращение промиса производится «вручную». Асинхронная функция всегда обертывается в новый промис.

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


Но что произойдет в том случае, если внутри асинхронной функции окажется ошибка?

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

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

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

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

А вот описание того, как работает fn-функция.

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

Эти паузы могут напоминать генераторы в ES6, но этому есть свои причины.

Решаем задачу

Ну а теперь давайте рассмотрим решение задачи, которая была указана выше.

В функции finishMyTask используется Await для ожидания результатов таких операций, как queryDatabase, sendEmail, logTaskInFile и других. Если же сравнивать это решение с тем, где использовались промисы, станет очевидным сходство. Тем не менее версия с Async/Await довольно сильно упрощает все синтаксические сложности. В этом случае нет большого количества коллбэков и цепочек вроде .then/.catch.

Вот решение с выводом чисел, здесь есть два варианта.

А вот решение с использованием async-функций.

Необработанные ошибки обертываются в rejected промис. Тем не менее в async-функциях можно использовать конструкцию try/catch для того, чтобы выполнить синхронную обработку ошибок.

canRejectOrReturn() — это асинхронная функция, которая либо удачно выполняется (“perfect number”), либо неудачно завершается с ошибкой (“Sorry, number too big”).

Поскольку в примере выше ожидается выполнение canRejectOrReturn, то собственное неудачное завершение повлечет за собой исполнение блока catch. В результате функция foo завершится либо с undefined (когда в блоке try ничего не возвращается), либо с error caught. В итоге у этой функции не будет неудачного завершения, поскольку try/catch займется обработкой самой функции foo.

Стоит уделить внимание тому, что в примере из foo возвращается canRejectOrReturn. Foo в этом случае завершается либо perfect number, либо возвращается ошибка Error (“Sorry, number too big”). Блок catch никогда не будет исполняться.

Проблема в том, что foo возвращает промис, переданный от
canRejectOrReturn. Поэтому решение функции foo становится решением для canRejectOrReturn. В этом случае код будет состоять всего из двух строк:

А вот что будет, если использовать вместе await и return:

В коде выше foo удачно завершится как с perfect number, так и с error caught. Здесь отказов не будет. Но foo завершится с canRejectOrReturn, а не с undefined. Давайте убедимся в этом, убрав строку return await canRejectOrReturn():

Распространенные ошибки и подводные камни

В некоторых случаях использование Async/Await может приводить к ошибкам.

Такое случается достаточно часто — перед промисом забывается ключевое слово await:

В коде, как видно, нет ни await, ни return. Поэтому foo всегда завершается с undefined без задержки в 1 секунду. Но промис будет выполняться. Если же он выдает ошибку или реджект, то в этом случае будет вызываться UnhandledPromiseRejectionWarning.

Async-функции в обратных вызовах

Async-функции довольно часто используются в .map или .filter в качестве коллбэков. В качестве примера можно привести функцию fetchPublicReposCount(username), которая возвращает количество открытых на GitHub репозиториев. Допустим, есть три пользователя, чьи показатели нам нужны. Вот код для этой задачи:

Нам нужны аккаунты ArfatSalman, octocat, norvig. В этом случае выполняем:

Стоит обратить внимание на Await в обратном вызове .map. Здесь counts — массив промисов, ну а .map — анонимный обратный вызов для каждого указанного пользователя.

Чрезмерно последовательное использование await

В качестве примера возьмем такой код:

Здесь в переменную count помещается число репо, затем это число добавляется в массив counts. Проблема кода в том, что пока с сервера не придут данные первого пользователя, все последующие пользователи будут находиться в режиме ожидания. Таким образом, в единый момент обрабатывается лишь один пользователь.

Если, например, на обработку одного пользователя уходит около 300 мс, то для всех пользователей это уже секунда, затрачиваемое время линейно зависит от числа пользователей. Но раз получение количества репо не зависит друг от друга, процессы можно распараллелить. Для этого нужна работа с .map и Promise.all:

Promise.all на входе получает массив промисов с возвращением промиса. Последний после завершения всех промисов в массиве или при первом реджекте завершается. Может случиться так, что все они не запустятся одновременно, — для того чтобы обеспечить одновременный запуск, можно использовать p-map.

Заключение

Async-функции становятся все более важными для разработки. Ну а для адаптивного использования async-функций стоит воспользоваться Async Iterators. JavaScript-разработчик должен хорошо разбираться в этом.

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