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

Конструкция async/await сделала большой вклад в асинхронное JS-программирование: можно использовать стиль синхронного кода для асинхронного.

Существенным плюсом async/await является возможность использования синхронного стиля программирования. Пример:

Очевидно, что код с использованием данной конструкции гораздо проще чем тот, что создан с помощью promise. Если игнорировать await, программу не отличить от любого другого синхронного языка, например Python.

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

async/await

Еще одним, но, пожалуй, менее очевидным преимуществом является async. Он объявляет, что функция getBooksByAuthor(), которая возвращает значение wait(), точно является promise. Поэтому можно безопасно вызывать getBooksByAuthorWithAwait().then.(..) или await getBooksByAuthorWithAwait().

В приведенном коде getBooksByAuthorWithPromise() может возвращать promise (обычный случай) или null (частный случай), но тогда нельзя будет безопасно вызвать .then().

Иногда сравнение с promise сопровождается утверждением, будто это эволюция асинхронного JS-программирования, что довольно спорно. async/await – лишь “приправа” для вашего кода, его небольшой апдейт, и никак не изменит ваш стиль программирования полностью.

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

Рассмотрим функции getBooksByAuthorWithwait() и getBooksByAuthorWithPromises() в приведенном выше примере. Помимо функциональной идентичности, они также имеют одинаковый интерфейс. Таким образом, getBooksByAuthorWithAwait() вернет promise, если вызвать его напрямую.

Какие ошибки можно допустить при использовании конструкции? Разберем наиболее характерные из них.

Слишком последовательно

Хоть await и может синхронизировать код, имейте в виду, что он по-прежнему является асинхронным. Поэтому необходимо позаботиться о том, чтобы он не был слишком последовательным.

Логически код кажется правильным, но вот вам опровержение:

  • await bookModel.fetchAll() ждет, пока не вернется fetchAll().
  • После этого вызывается await authorModel.fetch(authorId).

Хотим обратить ваше внимание на то, что authorModel.fetch(authorId) не зависит от bookModel.fetchAll(). Это означает, что их можно вызвать параллельно. Используя await, они станут последовательными, из-за чего время выполнения значительно возрастет.

Пример правильного кода:

Если вы хотите получить список элементов, необходимо полагаться на promise:

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

С помощью promises асинхронная функция имеет два возможных возвращаемых значения: resolved value (разрешенное) и rejected value (отклоненное). Можно использовать .then() для обычного случая и .catch() для частного. Но, к сожалению, обработка ошибок async/await – довольно сложный процесс. Рассмотрим популярные примеры.

Try. catch

В основном применение блока try. catch влечет за собой принятие rejected value как resolved value. Пример:

Преимущества использования try. catch:

  • Простота. Если вы умеете работать с такими языками, как Java или C++, у вас не будет никаких трудностей.
  • Чтобы обработать ошибки в конкретной части программы, в 1 блок try. catch можно обернуть несколько await.

Существует также один недостаток в этом подходе. try. catch будет находить каждое исключение в блоке, которые обычно не определяется с помощью promise. Пример:

При запуске данного кода выведется ошибка: ReferenceError: cb не определен. Так происходит из-за того, что BookModel вызывается несколько раз, и один вызов проглатывает «Error».

Функция возвращает оба значения

Можно также обработать ошибки с помощью языка Go. Так, будут возвращены и результат, и ошибка. Пример использования функции:

.catch

Вспомним функционал await: он будет ждать promise, чтобы завершить работу. Также помним, что promise.catch() возвращает promise. Зная это, вполне можно написать такую обработку:

В этом подходе есть две незначительные проблемы:

  • Это смесь promise и асинхрон-функций.
  • Обработка ошибок происходит перед телом программы, что интуитивно не понятно.

Конструкция async/await неплохо улучшает асинхронное JS-программирование: анализ и отладка становятся проще. Но для корректного использования этих инструментов необходимо полностью понимать promise, т. к. он лежит в их основе