Programação assíncrona com async / waitit

Bom dia amigos

As adições relativamente novas do JavaScript são funções assíncronas e a palavra-chave wait. Esses recursos são basicamente açúcar sintático sobre promessas (promessas), facilitando a gravação e a leitura de códigos assíncronos. Eles fazem o código assíncrono parecer síncrono. Este artigo irá ajudá-lo a descobrir o que é o quê.

Condições: conhecimento básico de informática, conhecimento dos conceitos básicos de JS, compreensão dos conceitos básicos de código assíncrono e promessas.
Objetivo: Compreender como as promessas são feitas e como são usadas.

Assíncrono / aguardar noções básicas


O uso de assíncrono / espera tem duas partes.

Palavra-chave assíncrona


Primeiro, temos a palavra-chave assíncrona, que colocamos antes da declaração da função para torná-la assíncrona. Uma função assíncrona é uma função que antecipa a capacidade de usar a palavra-chave wait para executar código assíncrono.

Tente digitar o seguinte no console do navegador:

function hello(){ return 'Hello' }
hello()

A função retornará 'Hello'. Nada incomum, certo?

Mas e se a transformarmos em uma função assíncrona? Tente o seguinte:

async function hello(){ return 'Hello' }
hello()

Agora, uma chamada de função retorna uma promessa. Esse é um dos recursos das funções assíncronas - elas retornam valores que são garantidos para serem convertidos em promessas.

Você também pode criar expressões funcionais assíncronas, assim:

let hello = async function(){ return hello() }
hello()

Você também pode usar as funções de seta:

let hello = async () => { return 'Hello' }

Todas essas funções fazem a mesma coisa.

Para obter o valor de uma promessa concluída, podemos usar o bloco .then ():

hello().then((value) => console.log(value))

... ou mesmo assim:

hello().then(console.log)

Assim, adicionar a palavra-chave assíncrona faz com que a função retorne uma promessa em vez de um valor. Além disso, ele permite funções síncronas para evitar qualquer sobrecarga associada à execução e ao suporte ao uso de aguardar. Uma simples adição de assíncrono na frente da função permite a otimização automática de código pelo mecanismo JS. Legal!

Palavra-chave aguardada


Os benefícios das funções assíncronas tornam-se ainda mais aparentes quando você os combina com a palavra-chave wait. Ele pode ser adicionado antes de qualquer função baseada em promessa para aguardar a conclusão da promessa e retornar o resultado. Depois disso, o próximo bloco de código é executado.

Você pode aguardar ao chamar qualquer função que retorne uma promessa, incluindo funções da API da Web.

Aqui está um exemplo trivial:

async function hello(){
    return greeting = await Promise.resolve('Hello')
}

hello().then(alert)

Obviamente, o código acima é inútil, serve apenas como uma demonstração da sintaxe. Vamos seguir em frente e ver um exemplo real.

Reescrevendo código em promessas usando async / waitit


Veja o exemplo de busca do artigo anterior:

fetch('coffee.jpg')
.then(response => response.blob())
.then(myBlob => {
    let objectURL = URL.createObjectURL(myBlob)
    let image = document.createElement('img')
    image.src = objectURL
    document.body.appendChild(image)
})
.catch(e => {
    console.log('There has been a problem with your fetch operation: ' + e.message)
})

Você já deve entender o que são promessas e como elas funcionam, mas vamos reescrever esse código usando async / waitit para ver como é simples:

async function myFetch(){
    let response = await fetch('coffee.jpg')
    let myBlob = await response.blob()

    let objectURL = URL.createObjectURL(myBlob)
    let image = document.createElement('img')
    image.src = objectURL
    document.body.appendChild(image)
}

myFetch().catch(e => {
    console.log('There has been a problem with your fetch operation: ' + e.message)
})

Isso torna o código muito mais simples e fácil de entender - sem .then () blocks!

O uso da palavra-chave async transforma uma função em promessa, para que possamos usar uma abordagem mista de promessas e aguardar, destacando a segunda parte da função em um bloco separado para aumentar a flexibilidade:

async function myFetch(){
    let response = await fetch('coffee.jpg')
    return await response.blob()
}

myFetch().then((blob) => {
    let objectURL = URL.createObjectURL(blob)
    let image = document.createElement('image')
    image.src = objectURL
    document.body.appendChild(image)
}).catch(e => console.log(e))

Você pode reescrever o exemplo ou executar nossa demonstração ao vivo (consulte também o código-fonte ).

Mas como isso funciona?


Embrulhamos o código dentro da função e adicionamos a palavra-chave assíncrona antes da palavra-chave da função. Você precisa criar uma função assíncrona para determinar o bloco de código no qual o código assíncrono será executado; aguardar só funciona dentro de funções assíncronas.

Mais uma vez: aguardar só funciona em funções assíncronas.

Dentro da função myFetch (), o código é muito parecido com a versão prometida, mas com algumas diferenças. Em vez de usar o bloco .then () após cada método baseado em promessa, basta adicionar a palavra-chave wait antes de chamar o método e atribuir um valor à variável. A palavra-chave waitit faz com que o mecanismo JS pause a execução do código em uma determinada linha, permitindo que outro código seja executado até que a função assíncrona retorne um resultado. Uma vez executado, o código continuará sendo executado a partir da próxima linha.
Por exemplo:

let response = await fetch('coffee.jpg')

O valor retornado pela promessa fetch () é atribuído à variável de resposta quando o valor fornecido se torna disponível, e o analisador para nessa linha até que a promessa seja concluída. Assim que o valor fica disponível, o analisador passa para a próxima linha de código que cria o Blob. Essa linha também chama o método assíncrono baseado em promessa, então aqui também usamos wait. Quando o resultado da operação retorna, retornamos da função myFetch ().

Isso significa que, quando chamamos a função myFetch (), ela retorna uma promessa, para que possamos adicionar .then () a ela, dentro da qual lidamos com a exibição da imagem na tela.

Você provavelmente pensa “Isso é ótimo!” E você está certo - menos .then () blocos para agrupar o código, tudo parece código síncrono, por isso é intuitivo.

Adicionar tratamento de erros


Se você deseja adicionar tratamento de erros, você tem várias opções.

Você pode usar a estrutura try ... catch síncrona com async / waitit. Este exemplo é uma versão estendida do código acima:

async function myFetch(){
    try{
        let response = await fetch('coffee.jpg')
        let myBlob = await response.blob()

        let objectURL = URL.createObjectURL(myBlob)
        let image = document.createElement('img')
        image.src = objectURL
        document.body.appendChild(image)
    } catch(e){
        console.log(e)
    }
}

myFetch()

O bloco catch () {} aceita um objeto de erro, que chamamos de "e"; Agora, podemos enviá-lo para o console. Isso nos permitirá receber uma mensagem sobre onde o erro ocorreu no código.

Se você quiser usar a segunda versão do código mostrado acima, você deve simplesmente continuar usando a abordagem híbrida e adicionar o bloco .catch () ao final da chamada .then (), da seguinte maneira:

async function myFetch(){
    let response = await fecth('coffee.jpg')
    return await response.blob()
}

myFetch().then((blob) => {
    let objectURL = URL.createObjectURL
    let image = document.createElement('img')
    image.src = objectURL
    document.body.appendChild(image)
}).catch(e => console.log(e))

Isso é possível porque o bloco .catch () captura erros que ocorrem na função assíncrona e na cadeia de promessas. Se você usar o bloco try / catch aqui, não poderá manipular erros que ocorrem quando a função myFetch () é chamada.

Você pode encontrar os dois exemplos no GitHub:
busca simples-assíncrona-espera-tentativa-captura.html (consulte a fonte )

busca simples-assíncrona-espera-promessa-captura.html (consulte a fonte )

Esperando Promise.all ()


O assíncrono / espera é baseado em promessas, para que você possa aproveitar ao máximo as últimas. Isso inclui Promise.all () em particular - você pode facilmente adicionar Aguardar Promise.all () para gravar todos os valores retornados de maneira semelhante ao código síncrono. Mais uma vez, pegue o exemplo do artigo anterior . Mantenha uma guia aberta para comparar com o código mostrado abaixo.

Com async / waitit (veja a demonstração ao vivo e o código-fonte ), fica assim:

async function fetchAndDecode(url, type){
    let repsonse = await fetch(url)

    let content

    if(type === 'blob'){
        content = await response.blob()
    } else if(type === 'text'){
        content = await response.text()
    }

    return content
}

async function displayContent(){
    let coffee = fetchAndDecode('coffee.jpg', 'blob')
    let tea = fetchAndDecode('tea.jpg', 'blob')
    let description = fetchAndDecode('description.txt', 'text')

    let values = await Promise.all([coffee, tea, description])

    let objectURL1 = URL.createObjectURL(values[0])
    let objectURL2 = URL.createObjectURL(values[1])
    let descText = values[2]

    let image1 = document.createElement('img')
    let image2 = document.createElement('img')
    image1.src = objectURL1
    image2.src = objectURL2
    document.body.appendChild(image1)
    document.body.appendChild(image2)

    let para = document.createElement('p')
    para.textContent = descText
    document.body.appendChild(para)
}

displayContent()
.catch(e => console.log(e))

Fizemos facilmente a função fetchAndDecode () assíncrona com algumas alterações. Preste atenção à linha:

let values = await Promise.all([coffee, tea, description])

Usando aguardar, obtemos os resultados de três promessas na variável values, de maneira semelhante ao código síncrono. Devemos agrupar toda a função em uma nova função assíncrona, displayContent (). Não conseguimos uma forte redução de código, mas conseguimos extrair a maior parte do código do bloco .then (), que fornece uma simplificação útil e torna o código mais legível.

Para lidar com erros, adicionamos um bloco .catch () à nossa chamada para displayContent (); Ele lida com erros de ambas as funções.

Lembre-se: você também pode usar o bloco .finally () para obter um relatório sobre a operação - você pode vê-lo em ação em nossa demonstração ao vivo (veja também o código-fonte ).

Desvantagens assíncronas / aguardam


Async / waitit tem algumas falhas.

O assíncrono / espera faz com que o código pareça síncrono e, de certa forma, faz com que ele se comporte de forma mais síncrona. A palavra-chave wait (espera) bloqueia a execução do código a seguir até que a promessa seja concluída, como acontece em uma operação síncrona. Isso permite que você execute outras tarefas, mas seu próprio código está bloqueado.

Isso significa que seu código pode ficar lento devido a um grande número de promessas pendentes, uma após a outra. Cada espera aguardará a conclusão da anterior, enquanto gostaríamos que as promessas fossem cumpridas simultaneamente, como se não estivéssemos usando async / waitit.

Há um padrão de design para atenuar esse problema - desativando todos os processos de promessa armazenando objetos Promise em variáveis ​​e aguardando-os. Vamos ver como isso é implementado.

Temos dois exemplos à nossa disposição: slow-async-waitit.html (consulte o código-fonte ) e fast-async-waitit.html (consulte o código-fonte ). Ambos os exemplos começam com uma função de promessa que imita uma operação assíncrona usando setTimeout ():

function timeoutPromise(interval){
    return new Promise((resolve, reject) => {
        setTimeout(function(){
            resolve('done')
        }, interval)
    })
}

Em seguida, segue a função assíncrona timeTest (), que espera três chamadas para timeoutPromise ():

async function timeTest(){
    ...
}

Cada uma das três chamadas para timeTest () termina com um registro do tempo que levou para cumprir a promessa e, então, o tempo necessário para concluir toda a operação é registrado:

let startTime = Date.now()
timeTest().then(() => {
    let finishTime = Date.now()
    let timeTaken = finishTime - startTime
    alert('Time taken in milliseconds: ' + timeTaken)
})

Em cada caso, a função timeTest () é diferente.

Em slow-async-waitit.html, timeTest () fica assim:

async function timeTest(){
    await timeoutPromise(3000)
    await timeoutPromise(3000)
    await timeoutPromise(3000)
}

Aqui, esperamos apenas três chamadas para timeoutPromise, cada vez que você definir um atraso de 3 segundos. Cada chamada aguarda a conclusão da anterior - se você executar o primeiro exemplo, verá uma janela modal em cerca de 9 segundos.

No fast-async-waitit.html, timeTest () se parece com isso:

async function timeTest(){
    const timeoutPromise1 = timeoutPromise(3000)
    const timeoutPromise2 = timeoutPromise(3000)
    const timeoutPromise3 = timeoutPromise(3000)

    await timeoutPromise1
    await timeoutPromise2
    await timeoutPromise3
}

Aqui, salvamos três objetos Promise em variáveis, o que faz com que os processos associados a ele sejam executados simultaneamente.

Além disso, esperamos seus resultados - à medida que as promessas começam a ser cumpridas simultaneamente, as promessas também serão concluídas ao mesmo tempo; Quando você executar o segundo exemplo, verá uma janela modal em cerca de 3 segundos!

Você deve testar cuidadosamente o código e manter isso em mente enquanto reduz o desempenho.

Outro pequeno inconveniente é a necessidade de agrupar as promessas esperadas em uma função assíncrona.

Usando async / waitit com classes


Concluindo, observamos que você pode adicionar assíncrono mesmo nos métodos para criar classes, para que eles retornem promessas e esperem promessas dentro delas. Pegue o código do artigo sobre JS orientado a objeto e compare-o com a versão modificada usando async:

class Person{
    constructor(first, last, age, gender, interests){
        this.name = {
            first,
            last
        }
        this.age = age
        this.gender = gender
        this.interests = interests
    }

    async greeting(){
        return await Promise.resolve(`Hi! I'm ${this.name.first}`)
    }

    farewell(){
        console.log(`${this.name.first} has left the building. Bye for now!`)
    }
}

let han = new Person('Han', 'Solo', 25, 'male', ['Smuggling'])

O método da classe pode ser usado da seguinte maneira:

han.greeting().then(console.log)

Suporte do navegador


Um dos obstáculos ao uso de assíncrono / espera é a falta de suporte para navegadores mais antigos. Esse recurso está disponível em quase todos os navegadores modernos, além de promessas; Existem alguns problemas no Internet Explorer e no Opera Mini.

Se você deseja usar async / waitit, mas precisa do suporte de navegadores mais antigos, pode usar a biblioteca BabelJS - ela permite usar o JS mais recente, convertendo-o em um navegador específico.

Conclusão


Async / waitit permite escrever código assíncrono fácil de ler e manter. Embora assíncrono / espera seja pior do que outras maneiras de escrever código assíncrono, definitivamente vale a pena explorar.

Obrigado pela atenção.

Feliz codificação!

All Articles