Поиск…
Вступление
async
и await
строить сверху обещаний и генераторов, чтобы выражать асинхронные действия inline. Это делает асинхронный или обратный код намного проще в обслуживании.
Функции с ключевым словом async
возвращают Promise
и могут быть вызваны с этим синтаксисом.
Внутри async function
ключевое слово await
может быть применено к любому Promise
и вызовет все тело функции после await
, после того как обещание будет разрешено.
Синтаксис
- асинхронная функция foo () {
...
ожидание asyncCall ()
} - async function () {...}
- async () => {...}
- (async () => {
const data = ожидание asyncCall ()
console.log (данные)}) ()
замечания
Асинхронные функции - это синтаксический сахар над обещаниями и генераторами. Они помогают сделать ваш код более читабельным, поддерживаемым, легче поймать ошибки и уменьшить количество отступов.
Вступление
Функция, определенная как async
является функцией, которая может выполнять асинхронные действия, но все равно выглядит синхронной. То, как это делается, - это использовать ключевое слово await
чтобы отложить функцию, пока она ожидает, что Promise решит или отклонит.
Примечание. Асинхронные функции - это предложение этапа 4 («Готово») на треке, которое должно быть включено в стандарт ECMAScript 2017.
Например, используя API Fetch на основе обещаний:
async function getJSON(url) {
try {
const response = await fetch(url);
return await response.json();
}
catch (err) {
// Rejections in the promise will get thrown here
console.error(err.message);
}
}
Функция async всегда возвращает сам Promise, поэтому вы можете использовать его в других асинхронных функциях.
Стиль функции стрелки
const getJSON = async url => {
const response = await fetch(url);
return await response.json();
}
Меньше отступа
С обещаниями:
function doTheThing() {
return doOneThing()
.then(doAnother)
.then(doSomeMore)
.catch(handleErrors)
}
С асинхронными функциями:
async function doTheThing() {
try {
const one = await doOneThing();
const another = await doAnother(one);
return await doSomeMore(another);
} catch (err) {
handleErrors(err);
}
}
Обратите внимание, как возврат находится внизу, а не вверху, и вы используете встроенную механику обработки ошибок ( try/catch
).
Ожидание и приоритет оператора
Вы должны учитывать приоритет оператора при использовании ключевого слова await
.
Представьте, что у нас есть асинхронная функция, которая вызывает другую асинхронную функцию getUnicorn()
которая возвращает Promise, которая разрешает экземпляр класса Unicorn
. Теперь мы хотим получить размер единорога, используя метод getSize()
этого класса.
Посмотрите на следующий код:
async function myAsyncFunction() {
await getUnicorn().getSize();
}
На первый взгляд это кажется правильным, но это не так. Из-за приоритета оператора это эквивалентно следующему:
async function myAsyncFunction() {
await (getUnicorn().getSize());
}
Здесь мы пытаемся вызвать getSize()
объекта Promise, чего мы не хотим.
Вместо этого мы должны использовать скобки для обозначения того, что сначала хотим дождаться единорога, а затем вызвать getSize()
результата:
async function asyncFunction() {
(await getUnicorn()).getSize();
}
Конечно. предыдущая версия может быть действительной в некоторых случаях, например, если getUnicorn()
была синхронной, но метод getSize()
был асинхронным.
Асинхронные функции по сравнению с обещаниями
async
функции не заменяют тип Promise
; они добавляют ключевые слова языка, которые облегчают вызов. Они взаимозаменяемы:
async function doAsyncThing() { ... }
function doPromiseThing(input) { return new Promise((r, x) => ...); }
// Call with promise syntax
doAsyncThing()
.then(a => doPromiseThing(a))
.then(b => ...)
.catch(ex => ...);
// Call with await syntax
try {
const a = await doAsyncThing();
const b = await doPromiseThing(a);
...
}
catch(ex) { ... }
Любая функция, использующая цепочки обещаний, может быть переписана с помощью await
:
function newUnicorn() {
return fetch('unicorn.json') // fetch unicorn.json from server
.then(responseCurrent => responseCurrent.json()) // parse the response as JSON
.then(unicorn =>
fetch('new/unicorn', { // send a request to 'new/unicorn'
method: 'post', // using the POST method
body: JSON.stringify({unicorn}) // pass the unicorn to the request body
})
)
.then(responseNew => responseNew.json())
.then(json => json.success) // return success property of response
.catch(err => console.log('Error creating unicorn:', err));
}
Функция может быть переписана с использованием async
/ await
следующим образом:
async function newUnicorn() {
try {
const responseCurrent = await fetch('unicorn.json'); // fetch unicorn.json from server
const unicorn = await responseCurrent.json(); // parse the response as JSON
const responseNew = await fetch('new/unicorn', { // send a request to 'new/unicorn'
method: 'post', // using the POST method
body: JSON.stringify({unicorn}) // pass the unicorn to the request body
});
const json = await responseNew.json();
return json.success // return success property of response
} catch (err) {
console.log('Error creating unicorn:', err);
}
}
Этот async
вариант newUnicorn()
кажется, возвращает Promise
, но на самом деле было несколько await
ключевых слов. Каждый из них возвратил Promise
, так что на самом деле у нас была коллекция обещаний, а не цепочка.
На самом деле мы можем думать об этом как о генераторе function*
, каждый из которых await
yield new Promise
. Тем не менее, результаты каждого обещания необходимы для продолжения функции. Вот почему необходимо дополнительное ключевое слово async
для функции (а также ключевое слово await
при вызове обещаний), поскольку оно сообщает Javascript автоматически создавать наблюдателя для этой итерации. Promise
возвращенная async function newUnicorn()
решает, когда эта итерация завершается.
Практически вам не нужно это учитывать; await
скрывает обещание и async
скрывает итерацию генератора.
Вы можете вызывать функции async
как если бы они были обещаниями, и await
каких-либо обещаний или любой async
функции. Вам не нужно await
асинхронной функции, так же, как вы можете выполнить обещание без .then()
.
Вы также можете использовать async
IIFE, если вы хотите немедленно выполнить этот код:
(async () => {
await makeCoffee()
console.log('coffee is ready!')
})()
Цикл с асинхронным ожиданием
При использовании async ждут в циклах, вы можете столкнуться с некоторыми из этих проблем.
Если вы просто попытаетесь использовать ожидание внутри forEach
, это вызовет Unexpected token
ошибку Unexpected token
.
(async() => {
data = [1, 2, 3, 4, 5];
data.forEach(e => {
const i = await somePromiseFn(e);
console.log(i);
});
})();
Это происходит из-за того, что вы ошибочно видели функцию стрелки как блок. await
будет в контексте функции обратного вызова, которая не является async
.
Интерпретатор защищает нас от создания вышеуказанной ошибки, но если вы добавите async
для обратного вызова forEach
ошибки не будут выбрасываться. Вы можете подумать, что это решает проблему, но она не будет работать должным образом.
Пример:
(async() => {
data = [1, 2, 3, 4, 5];
data.forEach(async(e) => {
const i = await somePromiseFn(e);
console.log(i);
});
console.log('this will print first');
})();
Это происходит потому, что функция async callback может только приостанавливаться, а не родительская асинхронная функция.
Вы можете написать функцию asyncForEach, которая возвращает обещание, и тогда вы можете что-то вроде
await asyncForEach(async (e) => await somePromiseFn(e), data )
В основном вы возвращаете обещание, которое разрешается, когда все обратные вызовы ожидаются и выполняются. Но есть лучшие способы сделать это, и это просто использовать цикл.
Вы можете использовать for-of
цикла или ее for/while
во for/while
цикла, это действительно не имеет значения , какой вы выбираете.
(async() => {
data = [1, 2, 3, 4, 5];
for (let e of data) {
const i = await somePromiseFn(e);
console.log(i);
}
console.log('this will print last');
})();
Но есть еще один улов. Это решение будет ожидать завершения каждого вызова для somePromiseFn
перед повторением следующего.
Это здорово, если вы действительно хотите, чтобы ваши вызовы somePromiseFn
исполнялись по порядку, но если вы хотите, чтобы они запускались одновременно, вам нужно будет await
на Promise.all
.
(async() => {
data = [1, 2, 3, 4, 5];
const p = await Promise.all(data.map(async(e) => await somePromiseFn(e)));
console.log(...p);
})();
Promise.all
получает массив обещаний в качестве единственного параметра и возвращает обещание. Когда все обещания в массиве будут решены, возвращенное обещание также будет разрешено. Мы await
этого обещания, и когда он будет разрешен, все наши ценности доступны.
Вышеприведенные примеры полностью выполняются. Функция somePromiseFn
может быть выполнена как функция асинхронного эха с тайм-аутом. Вы можете попробовать примеры в Babel-repl, по крайней мере, с предустановленной stage-3
и посмотреть на выход.
function somePromiseFn(n) {
return new Promise((res, rej) => {
setTimeout(() => res(n), 250);
});
}
Одновременные асинхронные (параллельные) операции
Часто вы хотите выполнять асинхронные операции параллельно. Существует прямой синтаксис , который поддерживает это в async
/ await
предложений, но так как await
будет ждать обещания, вы можете обернуть несколько обещаний вместе в Promise.all
ждать их:
// Not in parallel
async function getFriendPosts(user) {
friendIds = await db.get("friends", {user}, {id: 1});
friendPosts = [];
for (let id in friendIds) {
friendPosts = friendPosts.concat( await db.get("posts", {user: id}) );
}
// etc.
}
Это будет делать каждый запрос для регулярного получения сообщений каждого друга, но они могут выполняться одновременно:
// In parallel
async function getFriendPosts(user) {
friendIds = await.db.get("friends", {user}, {id: 1});
friendPosts = await Promise.all( friendIds.map(id =>
db.get("posts", {user: id})
);
// etc.
}
Это приведет к переходу по списку идентификаторов, чтобы создать массив обещаний. await
будет ждать все обещает быть полным. Promise.all
объединяет их в единое обещание, но они выполняются параллельно.