TL;DR:
- Ключевое слово await принимает функцию, которая возвращает обещание, и возвращает разрешенное значение этого обещания.
- Вы можете использовать ключевое слово
await
только в функциях, которые были объявлены с ключевым словомasync
. - Как
async
функции работают под капотом. - Вам не нужно использовать
await
в инструкцииreturn
функцииasync
при возврате обещания. - Вы можете использовать
Promise.all([…])
сawait
для одновременного выполнения нескольких обещаний. - Вы можете использовать цикл
for/await
для асинхронного цикла по обещаниям.
Вступление
ES2017 представил два ключевых слова, которые представили совершенно новую парадигму асинхронного программирования в JavaScript. async
и await
значительно упрощают использование обещаний и позволяют синхронно писать асинхронный код на основе обещаний. Несмотря на то, что async
и await
скрывают весь код, необходимый для простых промисов, по-прежнему очень важно знать, как работают промисы, и какой API они предоставляют, чтобы максимально использовать async/await
.
Если вы не знаете, как работают обещания, я рекомендую вам прочитать мои предыдущие две статьи о том, что вам нужно знать о обещаниях. Часть 1 описывает, когда промисы выполняются в цикле событий, а Часть 2 описывает, как работают промисы, предоставляемый ими API и некоторые общие шаблоны.
С учетом сказанного, давайте разберемся, как работает async/await
!
Ожидайте выражений
Ключевое слово await
принимает функцию, которая возвращает обещание, и возвращает разрешенное значение этого обещания. Если разрешение обещания выполнено, то возвращается выполненное значение, в противном случае оно бросает причину отклонения. Давайте посмотрим на пример:
const users = await fetch('/users');
Предполагая, что fetch(…)
возвращает Promise, выражение await fetch(…)
будет ждать, пока fetch(…)
не будет урегулировано. Если разрешение fetch(…)
является выполненным, то users
присваивается возвращаемое значение. Однако, если fetch(…)
по какой-либо причине обнаруживает ошибку и отклоняется, выражение await fetch(…)
выбрасывает причину отклонения.
Важно отметить, что выражение await
не заставляет вашу программу останавливаться и ничего не делать до тех пор, пока указанное обещание не будет выполнено. Вы можете думать о await
как о простом сокрытии .then(…, …)
вызова, который используется для обработки разрешения Promises.
Асинхронные функции
Поскольку любой код, использующий await
, является асинхронным, есть одно правило, которое вы должны обязательно соблюдать: вы можете использовать ключевое слово await
только в функциях, которые были объявлены с ключевым словом async
. Вот пример:
async getFoo() { const foo = await fetch('/api/v1/getFoo'); }
Обратите внимание, что когда вы объявляете функцию как async
, это означает, что функция вернет обещание, даже если в теле функции нет кода, связанного с обещаниями. Учтите следующее:
async addFiftyTwo(number) { const plusFiftyTwo = number + 52; return plusFiftyTwo; }
Под капотом
Когда вы посмотрите, как async
функции работают под капотом, вы увидите, что это просто функции, которые возвращают обещания, как описано в этом посте. Давайте посмотрим на пример:
async function get52() { return 52; } // The following works exactly the same function get52() { return new Promise((resolve, reject) => { resolve(52); }); } // Which can also be written as function get52() { return Promise.resolve(52); }
Вот почему все еще очень важно понимать концепции обещаний для эффективного использования async/await
. async/await
- это всего лишь синтаксический сахар вокруг обещаний, поэтому, если вы не знаете, как эффективно использовать обещания, вы, скорее всего, не сможете эффективно использовать и async/await
. Давайте теперь рассмотрим некоторые общие шаблоны, используемые с async/await
.
Общие шаблоны
Возвращение обещаний
Распространенная ошибка, которую делают новички, начиная с async/await
, - когда использовать await
, а когда не использовать. Учтите следующее:
async someFunc() { return await fetch('/api/v1/getFoo'); } async someOtherFunc() { const foo = await someFunc(); }
Как упоминалось ранее, функция async
возвращает обещание, которое разрешается до значения возвращенного из функции, а await
ожидает разрешения обещания перед продолжением выполнения функции async
. Если функция async
возвращает обещание как значение return
, вам не нужно использовать await
после return
ключевого слова. Посмотрим, как это будет выглядеть
async someFunc() { return fetch('/api/v1/getFoo'); } async someOtherFunc() { const foo = await someFunc(); }
Как бы вы это прочитали? someFunc()
возвращает обещание, которое разрешается в другое обещание, которое разрешается в ответ от вызова API. Итак, await
в someOtherFucn()
будет ждать разрешения самого внутреннего обещания и присвоить последнее значение переменной foo.
Ожидание нескольких обещаний
Предположим, вы написали getJSON
функцию, которая выглядит примерно так
async
function
getJSON(url)
{
const response
=
await
fetch(url);
const body
=
await
response.json();
return
body;
}
И теперь вы хотите получить некоторые данные JSON из внешнего API, поэтому вы делаете это
async function doSomething() { const users = await getJSON('/api/v1/users'); const themes = await getJSON('/api/v1/themes'); }
Время, необходимое для выполнения этой функции, полностью зависит от того, когда разрешены два getJSON
вызова. Это означает, что если первый вызов длится 5 секунд, второй вызов не начнется до тех пор, а если второй вызов также займет 5 секунд, то для завершения функции в целом потребуется 10 секунд. Это может быть ненужным, если два вызова не зависят друг от друга. Это очень распространенная ошибка, которую часто совершают новые разработчики при работе с async/await
. Так как же это исправить? Promise API предоставляет статический метод all([…])
, который мы можем использовать. Посмотрим, как это работает.
Примечание. Я обсуждаю Promise.all([…])
и другие методы, которые предоставляет Promise API, в моем сообщении здесь.
async function doSomething() { const [users, themes] = await Promise.all([ getJSON('/api/v1/users'), getJSON('/api/v1/themes') ]); }
Это выполнит оба getJSON()
вызова одновременно, что, в свою очередь, сократит время выполнения doSomething()
. Теперь вы можете подумать, зачем ставить await
перед Promise.all()
вместо getJSON
вызовов. Это потому, что Promise.all([…])
сам возвращает обещание, выполнение которого приводит к массиву, состоящему из значений из списка переданных обещаний. В сообщении, о котором я упоминал ранее, содержится более подробная информация об этом.
Асинхронные итерации
Итераторы и асинхронные итераторы выходят за рамки этой статьи, поэтому мы расскажем только о том, как async/await
используются для асинхронных итераций. Асинхронные итераторы похожи на обычные итераторы, но основаны на обещаниях и могут использоваться в новой форме for/of
циклов, for/await
.
Как и обычное выражение await
, цикл for/await
основан на обещаниях. Метод next()
на асинхронном итераторе создает обещание, цикл for/await
ожидает разрешения этого обещания, присваивает значение выполнения переменной цикла и запускает тело цикла. Затем он начинается заново, получая еще одно обещание от итератора и ожидая разрешения этого нового обещания. Давайте посмотрим на пример того, как это использовать. В следующем примере мы будем использовать обычный итератор (не асинхронный итератор), но применима та же концепция.
Предположим, у вас есть массив URL-адресов, которые вы хотите перебрать и получить массив обещаний:
const urls = [url1, url2, url3]; const promises = urls.map((url) => fetch(url));
Теперь мы можем использовать Promise.all ([…]) здесь и ждать, пока все обещания будут разрешены, но что, если нам нужны результаты, как только они вернутся? И что? Мы можем сделать что-то вроде этого:
for(const promise of promises) { const response = await promise; // do something with response here... }
Это вполне приемлемо, ничего плохого в этом нет. Поскольку итератор возвращает обещания, мы также можем воспользоваться циклом for/await
, как показано ниже:
for await (const response of promises) { //do something with response here... }
Цикл for/await
использует вызов await
в цикл и делает наш код немного более компактным, но два примера делают одно и то же. Следует отметить, что оба примера будут работать только в том случае, если они находятся внутри функций, объявленных async
: цикл for/await
в этом смысле совпадает с регулярным await
выражением.
Заключение
К настоящему времени вы должны хорошо понимать async/await
и то, как они работают. Мы рассмотрели ключевые слова по отдельности, а также заглянули внутрь того, как работают async
функции. После этого некоторые общие шаблоны, которые используются с async/await
, и некоторые типичные ошибки, которые допускают новые разработчики, когда начинают работать с async/await
.
Как всегда, дайте мне знать, каков ваш опыт работы с async / await. С какими проблемами вы столкнулись при использовании этого и других вопросов, по которым у вас могут возникнуть вопросы.
До следующего раза, ура!