Skip to content

async await

garevna edited this page Jan 10, 2019 · 18 revisions

🎓 async | await

ES7

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

В ES7 это достигается легко с помощью асинхронной функции

Для объявления асинхронной функции используется ключевое слово async ( перед function )

в теле асинхронной функции перед каждым выражением, возвращающим промис, ставится ключевое слово await

Результатом будет объект класса 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

⚠️ Асинхронная функция всегда возвращает промис

Поэтому нельзя конструктор объявить как асинхронную функцию - конструктор должен возвращать экземпляр объекта, а не обещание


🎓 Принцип работы

Асинхронная функция является промисом, который обещает, что все асинхронные операции внутри него будут выполнены в строго заданном порядке, но когда это произойдет - неизвестно, поскольку каждая из операций будет выполнятся неизвестно сколько времени

В принципе, того же эффекта можно достичь с помощью цепочки промисов

Однако асинхронная функция делает код линейным и прозрачным

Синхронизируются внутри асинхронной функции промисы

Ключевое слово await выполняет ту же роль, что и метод then() каждого промиса

Различие в том, что метод then() промиса запускает свой коллбэк асинхронно,

а ключевое слово await запускает функцию обратного вызова в нужное время

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

Итак, асинхронная функция является "оберткой" для нескольких асинхронных операций, которые строго упорядочиваются, т.е. их коллбэки вызываются в заданной последовательности

⚠️ Ключевое слово await можно использовать только внутри асинхронных функций
В противном случае будет сгенерировано исключение

⛔️ Uncaught SyntaxError: await is only valid in async function

☕ 1️⃣

function common ( message ) {
    return new Promise (
        ( resolve, reject ) => {
            setTimeout (
                () => resolve ( message ),
                Math.random() * 8000
            )
        }
    )
}

function first () {
    return common ( "first" )
}

function second () {
    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()

☕ 2️⃣

async function getUsersData ( userName ) {
    var userData = await fetch ( `https://api.github.com/users/${userName}` )
        .then ( response => response.json() )
    var userRepos = await fetch ( userData.repos_url )
        .then ( response => response.json() )
    return userRepos
}

getUsersData( 'josh' ).then ( repos => console.log ( repos ) )

☕ 3️⃣ Предположим, есть две функции, возвращающие промис:

var getNames = () => 
    new Promise ( ( resolve, reject ) => {
        setTimeout ( () => { resolve ( "Success" ) }, 1000 )  
    } )
var getPosts = () => 
    new Promise ( ( resolve, reject ) => { 
        setTimeout ( () => { resolve ( "Success" ) }, 1000 )
    } )

Каждый вызов длится 1 секунду
Нам нужно получить ответы от этих функций прежде, чем продолжить выполнение кода

Объявим асинхронную функцию:

async function getData () {
    console.time ( 'time' )
    var posts = await getPosts ().then ( response => response )
    var names = await getNames ().then ( response => response )
    console.timeEnd ( 'time' )
    console.info ( `names: ${ names } | posts: ${ posts }` )
}

Функция getData () фиксирует время начала операций
затем вызовет функцию getPosts () с ключевым словом await,
что означает, что пока асинхронный процесс функции getPosts ()
не завершится тем или иным образом,
выполнение кода будет приостановлено

Итак, getData () ждет завершения асинхронной операции getPosts (),
которая длится 1 сек.
Когда операция будет завершена
( т.е. промис функции getPosts () будет разрешен либо resolve, либо reject ),
функция getData () вызовет функцию getNames (),
опять-таки с ключевым словом await
Опять ожидание ( 1 сек ) завершения асинхронного процесса
функции getNames (), после чего функция getData ()
выведет в консоль суммарную продолжительность всех операций
и полученные результаты

Теперь вызовем функцию getData ()

getData ()

и посмотрим в консоль

2 сек. names: Success  | posts: Success

Аналогичного результата мы могли достичь с помощью цепочки промисов

Что плохо?

То, что несвязанные между собой асинхронные процессы выстраиваются в очередь
Суммарная продолжительность операций 2 сек

Посмотрим на альтернативный вариант

☕ 4️⃣

function getData ( typ ) {
    return new Promise ( function ( resolve, reject ) {
        setTimeout ( () => {
            console.log ( 'Promise resolved: ', typ )
            resolve ( typ )
        }, 1000 )
    })
}

async function getAllData () {
    console.time ( "Total" )
    let promises = Array.from ( arguments )
        .map ( x => getData ( x ) )
    await Promise.all ( promises )
         .then ( response => {
             console.timeEnd ( "Total" )
             console.log ( "response: ", response )
          })
}

getAllData ( "figures", "colors", "diameters" )

Функция getData () возвращает промис
Промис будет разрешен через 1 сек

Асинхронная функция getAllData ()
формирует массив промисов promises
и запускает сразу все асинхронные процессы
с помощью метода Promise.all ()

Перед вызовом метода Promise.all () стоит ключевое слово await`

Что происходит в этом случае:

Мы не выстраиваем очередь, а запускаем сразу все асинхронные процессы параллельно

Общая продолжительность операции не будет суммой продолжительности всех асинхронных процессов

В нашем примере вместо 3 секунд, которые мы получили бы в случае последовательной обработки запросов ( как в примере 1 ) мы получили общую продолжительность 1 сек

Используем fetch для получения данных трех типов: JSON, text, img

☕ 5️⃣

☕ 6️⃣

function getData ( typ ) {
    var __data = {
        colors: [ "blue", "green", "yellow", "orange", "red" ],
        figures: [ "triangle", "circle", "elipse", "square" ],
        diameters: [ 100, 200, 150, 250, 300 ]
    }
    return new Promise ( function ( resolve, reject ) {
        setTimeout ( () => {
            console.log ( 'Promise resolved: ', typ )
            Object.keys ( __data ).indexOf ( typ ) < 0 
                    ? reject ( "Data error" ) 
                    : resolve ( __data [ typ ] )
        }, Math.floor ( 2000 * Math.random() ) )
    })
}

async function getAllData () {
    let promises = []
    for ( var x of arguments ) {
        promises.push ( getData ( x ) )
    }
    return res = await Promise.all ( promises )
}

getAllData ( "figures", "colors", "diameters" )
    .then ( response => console.log ( 'response: ', response ) )

© 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