Introdução

A sintaxe assíncrono / aguardar em JavaScript ES7 facilita a coordenação de promessas. Se você tiver dados para buscar em diferentes bancos de dados ou APIs, de forma assíncrona e em uma determinada ordem, poderá acabar com um espaguete de promessas e retornos de chamada. A estrutura assíncrono / aguardar torna possível descrever sucintamente essa lógica com um código mais legível e fácil de manter.
Este artigo explica a sintaxe e a semântica do JavaScript async/await com diagramas e exemplos simples.
Mas primeiro, vamos começar com uma breve visão geral das promessas. Sinta-se à vontade para pular esta seção se estiver familiarizado com promessas em JS.
Resumo
- Introdução
- promessas
- O problema – Composto de promessas
- Função assíncrona
- Aguardam
- Manipulação de erros
- Discussão
promessas
Em JavaScript, uma promessa representa uma abstração de execução assíncrona sem bloqueio. As promessas JS são semelhantes a Java Futuros ou tarefa C#, se você já os encontrou.
As promessas são normalmente usadas para operações de rede e de E/S: por exemplo, ler um arquivo ou fazer uma chamada HTTP. Em vez de bloquear o fio runtime atual, criamos uma promessa assíncrona e usamos o método then para anexar um ligue de volta que será ativado quando o compromisso for concluído. O ligue de volta pode retornar uma promessa, e assim podemos encadear promessas de forma eficiente.
Para simplificar todos os exemplos, vamos supor que a biblioteca request-promise já foi instalado e carregado como:
const rp = require('request-promise');
Agora podemos fazer uma requisição HTTP GET que retorna uma promessa como esta:
const promessa = rp('http://example.com/');
A seguir, vejamos este exemplo:
console.log('Iniciando Execução'); const promessa = rq('http://example.com/'); promessa. then(resultado => console. log(resultado)); console.log("Não é possível saber se a promessa já terminou...");
Criamos uma nova promessa na linha 3 e anexamos um ligue de volta na linha 4. A promessa é assíncrona, portanto, quando chegamos à linha 6, não podemos dizer se a promessa está completa. Se executarmos o código várias vezes, os resultados poderão ser diferentes a cada vez.
De maneira mais geral, o código após qualquer promessa criada é executado simultaneamente com a promessa.
Não é possível bloquear a sequência de operação até que a promessa seja concluída. O que difere de Java Future.get, que bloqueia a fio funcionando até que um Future seja finalizado. Em JavaScript, você não pode esperar facilmente uma promessa.
O diagrama a seguir descreve o comportamento da amostra.

O processo de uma promessa.
A única maneira de programar código após uma promessa é especificar um ligue de volta através do método then.
La ligue de volta anexado via then só é executado após a conclusão da promessa. Se falhar (por exemplo, devido a um erro de rede), seu ligue de volta não será executado em mim. Para lidar com promessas falhadas, pode-se anexar outro callback via catch:
rp('http://example.com/'); .then(()=> console.log('Sucesso')) .catch(e =>console.log(`Falha. ${e}`));
Finalmente, para os testes, pode-se facilmente criar promessas “fictícias” que terminam sem/com erro, respectivamente, através dos métodos Promise.resolve et Promise.reject.
const sucesso = Promessa. resolve('Resolvido'); // Imprime "Resultado bem sucedido: Resolvido" success .then(result => console.log(`Resultado bem sucedido: ${result}`)) .catch(e => console.log(`Falha com: ${e}` )); const fail = Promise.Rejeite('Err'); //Impressora "Falha com: Err" success .then(result => console.log(`Resultado bem sucedido: ${result}`)) .catch(e => console.log(`Falha com: ${e}` ));
Para um tutorial mais detalhado sobre promises, convido você a ler este artigo.
O problema – Compondo promessas
Usar uma única promessa é bastante simples. No entanto, quando é necessário codificar uma lógica assíncrona complexa, pode-se combinar com sucesso várias promessas.
Escreva todas as cláusulas then e retornos de chamada assíncronos podem degenerar rapidamente.
Por exemplo, imagine que precisamos codificar um programa que:
- Faz uma chamada HTTP, aguarda a conclusão e imprime o resultado;
- E faça mais duas chamadas HTTP em paralelo;
- Assim que ambas as chamadas forem concluídas, imprima seu resultado.
O trecho de código a seguir mostra uma maneira de fazer isso:
// Faça a primeira chamada const call1promise = rp('http://example.com/'); call1promise.then( result => { // Feito após a primeira chamada terminar console.log(result); const call2promise = rp('http://example.com/'); const call3promise = rp('http: //example .com/'); return Promise.all([call2promise,call3promise]); } ).then( arr =>{ // Feito após a conclusão de ambas as chamadas console.log(arr[0]); console.log(arr[ 1]); });
Começamos fazendo a primeira chamada HTTP e programamos um retorno de chamada para processar o resultado da promessa.
No retorno de chamada, criamos duas outras promessas para futuras solicitações HTTP.
Essas duas promessas são concorrentes e exigem o estabelecimento de um ligue de volta quando ambos terminam. Devemos então combiná-los em uma única promessa via Promise.all, que cria uma promessa que termina assim que todas as promessas simultâneas são concluídas.
O resultado do primeiro ligue de volta é uma promessa, então temos que encadeá-los novamente com um ligue de volta then que exibirá os resultados.
O diagrama a seguir mostra o fluxo de execução:

Fluxo de execução da combinação de promessas.
Para um exemplo tão simples, terminamos com dois ligue de volta then e devemos usar Promise.all para sincronizar promessas concorrentes.
Mas e se tivéssemos mais operações assíncronas ou se tivéssemos que adicionar tratamento de erros?
Com essa abordagem, pode-se facilmente acabar com um código de espaguete consistindo em then, Promise.all e retorno de chamada.
Função assíncrona
Uma função assíncrona é um atalho para definir uma função que retorna uma promessa.
Por exemplo, as seguintes definições são equivalentes:
function f(){ return Promessa. resolve('TEST'); } //asyncF é equivalente a f! função assíncrona asyncF(){ return 'TESTE'; }
Da mesma forma, funções assíncronas que lançam exceções são equivalentes a funções que retornam promessas rejeitadas:
function f(){ return Promessa. rejeitar('Erro'); } //asyncF é equivalente a f função assíncrona asyncF(){ return 'Error'; }
Aguardam
Quando criamos uma promessa, só podemos esperar que ela seja concluída de forma assíncrona com um retorno de chamada via then. De fato, esperar por uma promessa não é possível para encorajar o desenvolvimento de código não bloqueante.
Caso contrário, os desenvolvedores ficariam tentados a realizar operações de bloqueio porque é mais fácil do que trabalhar com promessas e ligue de volta.
No entanto, para sincronizar promessas, precisamos ser capazes de esperar. Em outras palavras, se uma operação é assíncrona (ou seja, envolta em uma promessa), ela deve poder aguardar a conclusão de outra operação assíncrona.
Mas como o interpretador JavaScript poderia saber se uma operação está sendo executada em uma promessa ou não?
A resposta está no termo async. O interpretador JavaScript sabe que todas as operações em funções async será encapsulado em promessas e será assíncrono. Portanto, é possível permitir que outras promessas aguardem a conclusão.
Vamos descobrir agora await : Só pode ser usado com funções async, e permite esperar de forma síncrona por uma promessa. Se usarmos uma promessa fora de uma função async devemos usar o ligue de volta then.
async function f(){ // resposta será avaliada como o valor resolvido da promessa const response = await rp('http://example.com/'); console.log(resposta); } // Não podemos usar await fora da função assíncrona. // Precisamos usar callbacks then .... f().then(() => console.log('Finished'));
Agora, de volta ao problema da seção anterior:
// Encapsular a solução em uma função assíncrona async function solution() { // Aguarde a primeira chamada HTTP e imprima o resultado console.log(await rp('http://example.com/')); // Gera as chamadas HTTP sem esperar por elas - execute-as simultaneamente const call2promise = rp('http://example.com/'); // Não espera! const call3promise = rp('http://example.com/'); // Não espera! // Depois que ambos são gerados - espere por ambos const response2 = await call2promise; const resposta3 = espera call3promise; console.log(resposta2); console.log(resposta3); } // Chama a função assíncrona solution().then(() => console.log('Finished'));
Neste código, encapsulamos a solução em uma função async. Isso nos permite usar await por promessas, evitando assim ligue de volta then. Para depois invocar a função async que cria uma promessa para encapsular a lógica para invocar outras promessas.
De fato, no primeiro exemplo (sem async/await), as promessas foram executadas em paralelo e será o mesmo aqui. Observe que não usamos o await que na linha 11-12, bloqueamos a execução aqui até que ambas as promessas sejam concluídas.
Após estas linhas, sabemos que as promessas são encerradas como com o Promise.all(...).then(...) ) do exemplo anterior.
Sob o capô, await/async são traduzidos com promessas e retornos de chamada then. Basicamente, é apenas açúcar sintático para trabalhar com promessas. Toda vez que usamos await, o interpretador cria uma promessa e encapsula o restante das operações do async num ligue de volta then.
Considere o seguinte exemplo:
função assíncrona f() { console.log('Iniciando F'); const resultado = await rp('http://example.com/'); console.log(resultado); }
O fluxo de execução de f está descrito abaixo. Como f é async, ele é executado “paralelamente” à função que o chamou.

A função f executa e cria uma promessa. Neste ponto, o resto da função é encapsulado em um ligue de volta, então executado no final da promessa.
Manipulação de erros
Na maioria dos exemplos apresentados, assumimos que a promessa foi bem-sucedida e que, portanto, a espera por uma promessa retornou um valor.
Mas esteja ciente de que se uma promessa for rejeitada em uma função async e “esperado” através await, ele lançará uma exceção. É então necessário usar o padrão try/catch para lidar com essas exceções.
async function f() {
try {
const promesseResult = await Promise.reject('Error');
} catch (e){
console.log(e);
}
}
Se a função async não trata a exceção, que pode ser causada por uma promessa falhada ou algum outro bug, ele retornará uma promessa rejeitada.
async function f() { // Lança uma exceção const promiseResult = await Promise.reject('Error'); } // Impressora "Erro" f() .then(() => console.log('Sucesso')) .catch(err => console.log(err)); função assíncrona g() { throw "Erro"; } // Impressora "Erro" g() .then(() => console.log('Sucesso')) .catch(err => console.log(err));
Isso nos permite processar promessas rejeitadas por meio do mecanismo padrão de tratamento de erros.
Discussão
Async/await é uma estrutura de linguagem que complementa promessas, nos permite trabalhar com menos clichê. Por outro lado, async/await não substitui as promessas.
Por exemplo, se chamarmos uma função async de uma função normal ou de escopo global, não poderemos usar o await e teremos que usar as promessas.
função assíncrona fAsync() { // return promise.resolve(5) return 5; } // Não podemos usar await fAsync(). Use then/catch fAsync() .then(r => console.log(`resultado é ${r}`));
Normalmente eu tento encapsular a maioria da minha lógica assíncrona em uma ou mais funções async, e eu os chamo do meu código não assíncrono. O que minimiza a quantidade de then/catch retornos de chamada escrever.
O padrão async/await é sintático para ser mais conciso com promessas. Qualquer código com o padrão async/await pode ser reescrito com promessas simples. Em última análise, é uma questão de estilo.
Os acadêmicos gostam de notar que a simultaneidade e o paralelismo são diferentes. Eu deixo você assistir a conversa de Rob Pike sobre o assunto.
Simultaneidade é a capacidade de compor processos independentes (definição geral de processo) para trabalhar em conjunto. Quanto ao paralelismo, é poder executar vários processos simultaneamente.
A simultaneidade se concentra no design e na estrutura do aplicativo, enquanto o paralelismo é um modo de execução.
Por exemplo, vamos pegar um aplicativo multi-thread. A separação da aplicação em vários tópicos define seu modelo de competição. O mapeamento desses processos nos núcleos disponíveis define seu nível de paralelismo.
Um sistema concorrente pode funcionar em um único processador e, neste caso, não é paralelo.

Fonte: Await e Async explicados com diagramas e exemplos, Nikolai Grozev
Tradução por João Ribeiro
