Skip to content

async await

garevna edited this page Jul 5, 2019 · 18 revisions

🎓 async | await

ES7

Иногда бывает нужно упорядочить выполнение нескольких асинхронных операций

Мы уже можем решить такую задачу с помощью промиса и цепочки вызовов метода 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

Асинхронная функция является экземпляром класса 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 не является глобальным объектом

Попытка отратиться к объекту AsyncFunction вызовет исключение:

test instanceof AsyncFunction
🚫 Uncaught ReferenceError: AsyncFunction is not defined

поэтому получить ссылку на нее можно, например, так:

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" )

🎓 async function as a Promise

⚠️ Вызов асинхронной функции возвращает промис

⚠️ Если бы асинхронная функция не была промисом, при ее вызове мы получили бы блокирующую операцию

Поэтому с помощью асинхронной функции можно очень просто создавать промисы:

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

⚠️ Ключевое слово 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 ) )

🎓 async function vs Propmise.all

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 Пример в песочнице

© Irina H.Fylyppova 2018
Использование данных материалов или любой их части коммерческими школами ( курсами ) является нарушением авторских прав


Новая версия


1 2 3 4 5
6 7 8 9 10
11 12 13 14 15
16 17 18 19

Занятие 1

⤵️

Занятие 2

⤴️ ⤵️

Занятие 3

⤴️ ⤵️

Занятие 4

⤴️ ⤵️

Занятие 5

⤴️ ⤵️

Занятие 6

⤴️ ⤵️

Занятие 7

⤴️ ⤵️

Занятие 8

⤴️ ⤵️

Занятие 9

⤴️ ⤵️

Занятие 10

⤴️ ⤵️

Занятие 11

⤴️ ⤵️

Занятие 12

⤴️ ⤵️

Занятие 13

⤴️ ⤵️

Занятие 14

⤴️ ⤵️

Занятие 15

⤴️ ⤵️

Занятие 16

⤴️ ⤵️

Занятие 17

⤴️ ⤵️

Занятие 18

⤴️ ⤵️

Занятие 19

⤴️ ⤵️

⤴️

ico20 Дополнительно
dir-20 Справочная инфо

Clone this wiki locally