A construção assíncrona / aguardada apareceu no padrão ES7. Pode ser considerada uma melhoria notável no campo da programação assíncrona em JavaScript. Ele permite que você escreva um código que pareça síncrono, mas é usado para resolver tarefas assíncronas e não bloqueia o encadeamento principal. Apesar do fato de que assíncrono / espera é um ótimo recurso novo do idioma, usá-lo corretamente não é tão simples. O material, cuja tradução publicamos hoje, é dedicado a um estudo abrangente de assíncrono / espera e uma história sobre como usar esse mecanismo de maneira correta e eficaz.

Pontos fortes de assíncrono / aguardar
O benefício mais importante que um programador usando a construção assíncrona / espera obtém é que torna possível escrever código assíncrono em um estilo específico para o código síncrono. Compare o código escrito usando async / waitit com o código baseado em promessas.
// async/await async getBooksByAuthorWithAwait(authorId) { const books = await bookModel.fetchAll(); return books.filter(b => b.authorId === authorId); } // getBooksByAuthorWithPromise(authorId) { return bookModel.fetchAll() .then(books => books.filter(b => b.authorId === authorId)); }
É fácil perceber que a versão assíncrona / aguardada do exemplo é mais compreensível do que sua versão, na qual a promessa é usada. Se você não prestar atenção à palavra-chave
await
, esse código será semelhante a um conjunto regular de instruções executadas de forma síncrona - como no JavaScript familiar ou em qualquer outra linguagem síncrona como Python.
A atratividade de async / waitit não se deve apenas à maior legibilidade do código. Além disso, esse mecanismo possui excelente suporte ao navegador, o que não requer nenhuma solução alternativa. Portanto, hoje as funções assíncronas são totalmente compatíveis com todos os principais navegadores.
Todos os principais navegadores suportam funções assíncronas ( caniuse.com )Esse nível de suporte significa, por exemplo, que o código usando async / waitit não precisa ser
transposto . Além disso, facilita a depuração, o que talvez seja ainda mais importante do que a falta de necessidade de transpilação.
A figura a seguir mostra o processo de depuração de uma função assíncrona. Aqui, ao definir um ponto de interrupção na primeira instrução da função e ao executar o comando Step Over, quando o depurador atingir a linha em que a palavra-chave
await
é usada, você poderá observar como o depurador pausa por um tempo, aguardando o
bookModel.fetchAll()
função
bookModel.fetchAll()
e, em seguida, pula para a linha em que o comando
.filter()
é
.filter()
! Esse processo de depuração parece muito mais simples do que as promessas de depuração. Aqui, ao depurar código semelhante, você teria que definir outro ponto de interrupção na linha
.filter()
.
Depurando uma função assíncrona. O depurador aguardará a conclusão da linha de espera e passará para a próxima linha após a conclusão da operaçãoOutro ponto forte do mecanismo em consideração, que é menos óbvio do que o que já examinamos, é a presença da palavra
async
chave
async
aqui. No nosso caso, seu uso garante que o valor retornado por
getBooksByAuthorWithAwait()
seja uma promessa. Como resultado, você pode usar com segurança a construção
getBooksByAuthorWithAwait().then(...)
ou
await getBooksByAuthorWithAwait()
construção
await getBooksByAuthorWithAwait()
no código que chama essa função. Considere o seguinte exemplo (observe que isso não é recomendado):
getBooksByAuthorWithPromise(authorId) { if (!authorId) { return null; } return bookModel.fetchAll() .then(books => books.filter(b => b.authorId === authorId)); } }
Aqui, a função
getBooksByAuthorWithPromise()
pode, se estiver tudo bem, retornar uma promessa ou, se algo der errado -
null
. Como resultado, se ocorrer um erro, você não poderá chamar
.then()
com segurança
.then()
. Ao declarar funções usando a
async
erros desse tipo são impossíveis.
Sobre a percepção errônea de assíncrono / aguardar
Em algumas publicações, a construção assíncrona / espera é comparada às promessas e diz-se que representa a próxima geração da evolução da programação JavaScript assíncrona. Com isso, com todo o respeito devido aos autores de tais publicações, permito-me discordar. O assíncrono / espera é uma melhoria, mas não passa de "açúcar sintático", cuja aparência não leva a uma mudança completa no estilo de programação.
Em essência, funções assíncronas são promessas. Antes de um programador poder usar adequadamente a construção assíncrona / aguardada, ele deve estudar bem as promessas. Além disso, na maioria dos casos, trabalhando com funções assíncronas, você precisa usar promessas.
Dê uma olhada nas
getBooksByAuthorWithAwait()
e
getBooksByAuthorWithPromises()
do exemplo acima. Observe que eles são idênticos não apenas em termos de funcionalidade. Eles também têm exatamente as mesmas interfaces.
Tudo isso significa que se você chamar diretamente a função
getBooksByAuthorWithAwait()
, ela retornará a promessa.
De fato, a essência do problema que estamos falando aqui é a percepção incorreta do novo design, quando cria uma sensação enganosa de que uma função síncrona pode ser convertida em assíncrona devido ao uso simples do
async
e
await
palavras-chave e não pensar em mais nada.
Armadilhas do assíncrono / aguardam
Vamos falar sobre os erros mais comuns que podem ser cometidos usando async / waitit. Em particular, sobre o uso irracional de chamadas sucessivas de funções assíncronas.
Embora a palavra-chave
await
possa fazer com que o código pareça síncrono, use-o, vale lembrar que o código é assíncrono, o que significa que você precisa ter muito cuidado com a chamada seqüencial de funções assíncronas.
async getBooksAndAuthor(authorId) { const books = await bookModel.fetchAll(); const author = await authorModel.fetch(authorId); return { author, books: books.filter(book => book.authorId === authorId), }; }
Esse código, em termos de lógica, parece correto. No entanto, há um problema sério. É assim que funciona.
- As chamadas do sistema
await bookModel.fetchAll()
e aguardam a .fetchAll()
comando .fetchAll()
. - Após receber o resultado de
bookModel.fetchAll()
await authorModel.fetch(authorId)
será chamado.
Observe que a chamada para
authorModel.fetch(authorId)
é independente dos resultados da chamada para
bookModel.fetchAll()
e, de fato, esses dois comandos podem ser executados em paralelo. No entanto, o uso de
await
resulta nessas duas chamadas sendo executadas seqüencialmente. O tempo total de execução seqüencial desses dois comandos será maior que o tempo de execução paralela.
Aqui está a abordagem correta para escrever esse código:
async getBooksAndAuthor(authorId) { const bookPromise = bookModel.fetchAll(); const authorPromise = authorModel.fetch(authorId); const book = await bookPromise; const author = await authorPromise; return { author, books: books.filter(book => book.authorId === authorId), }; }
Considere outro exemplo do uso indevido de funções assíncronas. Isso ainda é pior do que no exemplo anterior. Como você pode ver, para carregar de forma assíncrona uma lista de certos elementos, precisamos confiar nas possibilidades de promessas.
async getAuthors(authorIds) { // , // const authors = _.map( // authorIds, // id => await authorModel.fetch(id)); // const promises = _.map(authorIds, id => authorModel.fetch(id)); const authors = await Promise.all(promises); }
Em poucas palavras, para usar corretamente funções assíncronas, você precisa, como no momento em que isso não era possível, primeiro pense em operações assíncronas e depois escreva o código usando
await
. Em casos complexos, provavelmente será mais fácil usar promessas diretamente.
Tratamento de erros
Ao usar promessas, a execução do código assíncrono pode terminar conforme o esperado - eles dizem que a promessa foi resolvida com sucesso ou com um erro - e dizem que a promessa foi rejeitada. Isso nos permite usar
.then()
e
.catch()
, respectivamente. No entanto, o tratamento de erros usando o mecanismo assíncrono / espera pode ser complicado.
▍ construção try / catch
A maneira padrão de lidar com erros ao usar async / waitit é com a construção try / catch. Eu recomendo usar essa abordagem. Ao fazer uma chamada em espera, o valor retornado quando a promessa é rejeitada é apresentado como uma exceção. Aqui está um exemplo:
class BookModel { fetchAll() { return new Promise((resolve, reject) => { window.setTimeout(() => { reject({'error': 400}) }, 1000); }); } }
O erro detectado no
catch
é exatamente o valor obtido quando a promessa é rejeitada. Depois de capturar uma exceção, podemos aplicar várias abordagens para trabalhar com ela:
- Você pode manipular a exceção e retornar o valor normal. Se você não usar a expressão de
return
no catch
para retornar o que é esperado após a execução da função assíncrona, isso será equivalente ao uso do comando return undefined
; - Você pode simplesmente passar o erro para o local onde o código que falhou foi chamado e permitir que ele seja processado lá. Você pode gerar um erro diretamente usando um comando como
throw error;
, que permite usar a função async getBooksByAuthorWithAwait()
na cadeia de promessas. Ou seja, ele pode ser chamado usando a construção getBooksByAuthorWithAwait().then(...).catch(error => ...)
. Além disso, você pode agrupar o erro em um objeto Error
, que pode parecer throw new Error(error)
. Isso permitirá, por exemplo, ao enviar informações de erro para o console, exibir a pilha de chamadas completa. - O erro pode ser representado como uma promessa rejeitada, parece
return Promise.reject(error)
. Nesse caso, isso é equivalente ao comando throw error
, não sendo recomendado.
Aqui estão os benefícios do uso da construção try / catch:
- Tais ferramentas de tratamento de erros existem na programação há muito tempo, são simples e compreensíveis. Digamos, se você tiver experiência em programação em outras linguagens, como C ++ ou Java, entenderá facilmente o dispositivo try / catch em JavaScript.
- Você pode fazer várias chamadas em espera em um bloco try / catch, o que permite lidar com todos os erros em um só lugar, se você não precisar lidar separadamente com erros em cada etapa da execução do código.
Note-se que há uma desvantagem no mecanismo try / catch. Como try / catch captura todas as exceções que ocorrem no bloco
try
, essas exceções não relacionadas a promessas também serão inseridas no manipulador de
catch
. Dê uma olhada neste exemplo.
class BookModel { fetchAll() { cb(); // , `cb` , return fetch('/books'); } } try { bookModel.fetchAll(); } catch(error) { console.log(error); // "cb is not defined" }
Se você executar esse código, verá a mensagem de erro
ReferenceError: cb is not defined
no console. Esta mensagem é emitida pelo comando
console.log()
do
catch
, e não pelo próprio JavaScript. Em alguns casos, esses erros levam a graves consequências. Por exemplo, se chamar
bookModel.fetchAll();
Se você estiver oculto em uma série de chamadas de função e uma das chamadas "engolir" um erro, será muito difícil detectar esse erro.
Return Retorno de função de dois valores
A inspiração para a próxima maneira de lidar com erros no código assíncrono é Go. Ele permite que funções assíncronas retornem um erro e um resultado. Leia mais sobre isso
aqui .
Em poucas palavras, funções assíncronas, com esta abordagem, podem ser usadas assim:
[err, user] = await to(UserModel.findById(1));
Pessoalmente, não gosto disso, porque esse método de tratamento de erros introduz o estilo de programação Go no JavaScript, que parece não natural, embora, em alguns casos, possa ser muito útil.
▍Utilização de .catch
A maneira final de lidar com os erros, sobre os quais falaremos, é usar
.catch()
.
Pense em como a
await
funciona. Ou seja, o uso dessa palavra-chave faz com que o sistema aguarde até que a promessa conclua seu trabalho. Além disso, lembre-se de que um comando no formato
promise.catch()
também retorna uma promessa. Tudo isso sugere que erros de função assíncrona podem ser manipulados assim:
// books undefined , // catch let books = await bookModel.fetchAll() .catch((error) => { console.log(error); });
Dois pequenos problemas são característicos dessa abordagem:
- Essa é uma mistura de promessas e funções assíncronas. Para usar isso, é necessário, como em outros casos semelhantes, entender as características do trabalho das promessas.
- Essa abordagem não é intuitiva, pois o tratamento de erros é realizado em um local incomum.
Sumário
A construção assíncrona / aguardada, que foi introduzida no ES7, é definitivamente uma melhoria nos mecanismos de programação assíncrona do JavaScript. Isso pode facilitar a leitura e o código de depuração. No entanto, para usar corretamente o assíncrono / aguardar, é necessário um profundo entendimento das promessas, uma vez que o assíncrono / aguardar é apenas "açúcar sintático" baseado em promessas.
Esperamos que este material tenha permitido que você se familiarize com async / wait, e o que você aprendeu aqui o salvará de alguns erros comuns que surgem ao usar essa construção.
Caros leitores! Você usa a construção assíncrona / espera no JavaScript? Nesse caso, informe-nos como você lida com erros no código assíncrono.
