async await - garevna/js-course GitHub Wiki
Иногда бывает нужно упорядочить выполнение нескольких асинхронных операций
Мы уже можем решить такую задачу с помощью промиса и цепочки вызовов метода then
Однако есть альтернативный вариант - асинхронная функция
По факту, это просто альтернативный синтаксис того же промиса - вызов асинхронной функции возвращает промис
т.е. она обещает, что все асинхронные операции внутри нее будут выполнены в строго заданном порядке, но когда это произойдет - неизвестно, поскольку каждая из операций будет выполнятся неизвестно сколько времени
Это означает, что результат работы асинхронной функции нужно ловить в коллбэке метода then
Для объявления асинхронной функции используется ключевое слово async ( перед function )
async function sigma () {
...
}в теле асинхронной функции перед каждым выражением, возвращающим промис, ставится ключевое слово await
☕ 1
Тут мы внутри асинхронной функции объявляем функцию promise, которая возвращает промис, при этом биндит колбэку resolve первый аргумент, переданный функции promise
Второй аргумент функции promise используется для установки таймера
async function sigma () {
function promise () {
return new Promise (
resolve => {
setTimeout (
resolve.bind ( null, arguments[0] ),
arguments[1] * 1000
)
}
)
}
console.log ( await promise( "Start", 5 ) )
console.log ( await promise( "Continue", 3 ) )
console.log ( await promise( "End", 2 ) )
return "Finish"
}
sigma().then ( response => console.log ( response ) )Асинхронная функция является экземпляром класса AsyncFunction
async function test () {}
console.dir ( test )▼ async ƒ test()
arguments: (...)
caller: (...)
length: 0
name: "test"
▼ __proto__: AsyncFunction
arguments: (...)
caller: (...)
► constructor: ƒ AsyncFunction()
Symbol(Symbol.toStringTag): "AsyncFunction"
▼ __proto__: ƒ ()
► apply: ƒ apply()
arguments: (...)
► bind: ƒ bind()
► call: ƒ call()
caller: (...)
► constructor: ƒ Function()
length: 0
name: ""
► toString: ƒ toString()
► Symbol(Symbol.hasInstance): ƒ [Symbol.hasInstance]()
► get arguments: ƒ ()
► set arguments: ƒ ()
► get caller: ƒ ()
► set caller: ƒ ()
► __proto__: ObjectПопытка отратиться к объекту AsyncFunction вызовет исключение:
test instanceof AsyncFunctionпоэтому получить ссылку на нее можно, например, так:
var AsyncFunctionConstructor = test.__proto__.constructorили:
var AsyncFunction = ( async function () {} ).__proto__.constructorТеперь исключения не будет:
test instanceof AsyncFunction // trueБолее того, мы можем теперь использовать ссылку на конструктор AsyncFunction для создания экземпляра асинхронной функции:
const asyncFunc = new AsyncFunction
console.log ( asyncFunc )Result
async ƒ anonymous(
) {
}Но и это еще не все 😉
☕ 2
Давайте немного "порезвимся" и добавим в прототип конструктора асинхронных функций метод waitFor:
( async function () {} )
.__proto__
.waitFor = ( message, time ) =>
new Promise (
resolve => setTimeout (
() => resolve ( message ),
time * 1000
)
)а теперь создадим асинхронную функцию sample:
const sample = async ( message, time ) =>
console.log (
await sample.waitFor ( message, time )
)Осталось только вызвать функцию sample:
console.log ( "Start" )
sample( "Hello", 3 )
console.log ( "End" )Поэтому с помощью асинхронной функции можно очень просто создавать промисы:
☕ 3
console.log ( "start" )
const sample = async resolve => resolve( "hello" )
sample( response => response )
.then( response => console.log ( response ) )
console.log ( "..." )
const sigma = async resolve => await resolve( "baby" )
sigma( response => response )
.then( response => console.log ( response ) )
console.log ( "end" )Обе асинхронные функции ( sample и sigma ) получают в качестве аргумента простейшую колбэк-функцию response => response, которая возвращает то, что получает
В первом случае мы не использовали ключевое слово await в теле асинхронной функции sample, а во втором ( функция sigma ) мы вызываем колбэк-функцию с ключевым словом await
Обратите внимание на последовательность выполнения коллбэков:
сначала выполняется весь синхронный код,
а затем возвращаются из Event Loop колбэки и отрабатывают в той последовательности, в которой были вызваны их асинхронные функции
start
...
end
hello
babyТаким образом, промисы можно создавать без конструктора Promise 😉
☕ 4
Разберите работу кода:
let promise = message => new Promise (
resolve => {
let time = Math.round ( Math.random() * 3000 )
setTimeout (
() => resolve ( `${message}: ${time}` ),
time
)
}
)
async function test () {
return await promise (
await promise (
await promise ( "start" )
)
)
}
test().then ( response => console.log ( response ) )Результат в консоли:
start: 2669: 2775: 477В принципе, того же эффекта можно достичь с помощью цепочки промисов
Однако асинхронная функция делает код линейным и прозрачным
Внутри асинхронной функции синхронизируются вызовы коллбэков промисов
await можно использовать только внутри асинхронных функций
В противном случае будет сгенерировано исключение
⛔ Uncaught SyntaxError: await is only valid in async function
await вызывает метод then() объекта, следующего за await
☕ 5
Обратите внимание на последовательность вывода в консоль:
async function hello ( message ) {
console.log ( message )
return "I've finished"
}
console.log ( "Start" )
hello( "Hello!" ).then ( response => console.log ( response ) )
console.log ( "Finish" )Результат:
Start
Hello!
Finish
I've finishedПри отсутствии await код внутри асинхроной функции отработал в основном потоке, синхронно
Сама функция вернула промис, который резолвится строкой "Finish"
А теперь используем ключевое слово await:
async function hello ( message ) {
console.log ( await message )
return "I've finished"
}
console.log ( "Start" )
hello( "Hello!" ).then ( response => console.log ( response ) )
console.log ( "Finish" )Последовательность вывода в консоль изменилась 😉
Результат:
Start
Finish
Hello!
I've finishedТеперь передадим функции hello не строку "Hello!", а промис, который резолвится этой строкой:
async function hello ( message ) {
console.log ( await message )
return "I've finished"
}
console.log ( "Start" )
hello( new Promise (
resolve => resolve ( "Hello!" )
) ).then ( response => console.log ( response ) )
console.log ( "Finish" )Результат:
Start
Finish
Hello!
I've finishedКак мы видим, результат идентичен предыдущему
Вывод: если после await следует значение, то это значение "заврачивается" в промис, который тут же резолвится этим значением
Это означает, что создавать асинхронщину с помощью асинхронной функции очень легко 😉
☕ 6
let num = 5
async function sample ( arg ) {
num = await arg
}
sample ( 10 ).then ( () => console.log ( `async: ${num}` ) )
console.log ( num )☕ 7
async function getUser ( userNum ) {
return await (
await fetch (`https://api.github.com/users/${userNum}`)
).json()
}
getUser ( 5 )
.then ( response => console.log ( response ) )then() у объекта, который будет после ключевого слова await, произойдет вызов метода then()
Сам объект при этом вовсе не обязательно должен быть промисом 😉
☕ 8
const browsers = [ "Chrome", "Mozilla", "Safari", "IE" ]
browsers.then = ( function () {
let current = 0
return function ( resolve ) {
let response = {
value: this [ current++ ],
done: current < this.length
}
setTimeout (
() => resolve.call ( null, response ),
1000
)
}
})()
async function showBrowsers () {
console.log ( await browsers )
console.log ( await browsers )
console.log ( await browsers )
console.log ( await browsers )
console.log ( await browsers )
}
showBrowsers ()☕ 9
let user = {
name: "Stephan",
then: function ( resolve, reject ) {
console.log ( `Begin with ${this.name}` )
Math.random() > 0.5 ?
resolve ( `Access granted for ${this.name}` ) :
reject ( `Access denied for ${this.name}` )
}
}
async function showMustGoOn ( obj ) {
console.log ( await obj )
}
console.log ( "Start" )
showMustGoOn ( user )
console.log ( "Finish" )Если случайное число окажется больше 0.5, то мы увидим в консоли такую картинку:
Start
Finish
Begin with Stephan
Access granted for Stephanв противном случае картинка будет такой:
Start
Finish
Begin with Stephan
undefined
► Uncaught (in promise) Access denied for StephanИтак, await вызывает метод then объекта user
Итак, асинхронная функция является "оберткой" для нескольких асинхронных операций, которые строго упорядочиваются, т.е. их коллбэки отрабатывают в заданной последовательности
☕ 10
Предположим, есть три функции: first, second и third, которые возвращают промис
Асинхронные процессы
function common ( message ) {
return new Promise (
( resolve, reject ) => {
setTimeout (
() => resolve ( message ),
Math.random() * 8000
)
}
)
}
function first () {
return common ( "first" )
}
function () {
return common ( "second" )
}
function third () {
return common ( "third" )
}Задача - синхронизировать вызов коллбэков так,
чтобы первым в консоль было выведено "first", затем "second", а затем "third"
Аасинхронный код выводит их в случайном порядке:
first().then( res => console.log ( res) )
second().then( res => console.log ( res) )
third().then( res => console.log ( res) )цепочка промисов
first().then(
res => {
console.log ( res )
second().then(
res => {
console.log ( res )
third().then(
res => console.log ( res )
)
}
)
}
)асинхронная функция
let test = async () => {
console.log ( await first() )
console.log ( await second() )
console.log ( await third() )
}
test()Как видно из примера, код асинхронной функции проще, чем цепочка промисов 😉
☕ 11
async function getUsersData ( userName ) {
let userData = await (
await fetch ( `https://api.github.com/users/${userName}` )
).json()
const addElem = tagName =>
document.body.appendChild (
document.createElement ( tagName )
)
addElem ( "img" ).src = userData.avatar_url
let userRepos = await ( await fetch ( userData.repos_url ) ).json()
for ( let item of userRepos )
addElem ( "div" ).innerText = item.events_url
return "Ready"
}
getUsersData( 'josh' )
.then ( event => console.log ( event ) )☕ 12
Синхронизация асинхронных процессов приводит к увеличению времени выполнения
Предположим, есть две функции, возвращающие промис:
var getNames = () =>
new Promise ( ( resolve, reject ) =>
setTimeout (
() => resolve ( "Names" ),
1000
)
)
var getPosts = () =>
new Promise ( ( resolve, reject ) =>
setTimeout (
() => resolve ( "Posts" ),
1000
)
)Каждый вызов длится 1 секунду
Если мы будем использовать асинхронную функцию для последовательного вызова getNames и getPosts, то суммарная продолжительность выполнения этих двух асинхронных операций составит не менее 2 сек
async function getData () {
console.time ( 'time' )
var posts = await getPosts ()
var names = await getNames ()
console.timeEnd ( 'time' )
console.info ( `\n${ names } | ${ posts }\n\n` )
}
getData ()Результат в консоли
Names | Posts
time: 2002.258056640625msЧто плохо?
То, что несвязанные между собой асинхронные процессы выстраиваются в очередь
Посмотрим на альтернативный вариант
function getData () {
console.time ( 'time' )
Promise.all ([
getNames (),
getPosts ()
])
.then (
result => {
console.info ( `\n${ result[0] } | ${ result[1] }\n\n` )
console.timeEnd ( 'time' )
}
)
}Результат в консоли
Names | Posts
time: 1001.474365234375ms☕ 13
function getData ( typ ) {
return new Promise ( function ( resolve, reject ) {
setTimeout ( () => {
console.log ( 'Promise resolved: ', typ )
resolve ( typ )
}, 1000 )
})
}
function getAllData () {
console.time ( "Total" )
let promises = Array.from ( arguments )
.map ( x => getData ( x ) )
Promise.all ( promises )
.then ( response => {
console.timeEnd ( "Total" )
console.log ( "response: ", response )
})
}
getAllData ( "figures", "colors", "diameters" )Функция getData () возвращает промис
Промис будет разрешен через 1 сек
Функция getAllData () формирует массив промисов promises и запускает сразу все асинхронные процессы с помощью метода Promise.all ()
Что происходит в этом случае:
Мы не выстраиваем очередь, а запускаем сразу все асинхронные процессы параллельно
Однако упорядоченность возвращаемых данных контролирует Promise.all ()
В возвращаемом массиве данные будут упорядочены в той последовательности, в которой упорядочены промисы в массиве промисов
В этом случае Promise.all () является удобной альтернативой асинхронной функции
Общая продолжительность операции не будет суммой продолжительности всех асинхронных процессов
В этом примере вместо 3 секунд, которые выполнялся бы код в случае последовательной обработки запросов, общая продолжительность составила 1 сек
| ☕ 14 | Пример в песочнице |
|---|