Olá Habr! Apresento a você a tradução do artigo
“JavaScript Assíncrono / Espera e Promessas: Explicado como se você tivesse
cinco anos de idade” por Jack Pordi.
Qualquer pessoa que se considere um desenvolvedor JavaScript, em algum momento, deve ter encontrado funções de retorno de chamada, promessas ou, mais recentemente, a sintaxe assíncrona / aguardada. Se você está no jogo há tempo suficiente, provavelmente já encontrou momentos em que as funções de retorno de chamada aninhadas eram a única maneira de obter assincronia no JavaScript.
Quando comecei a aprender e escrever em JavaScript, já havia um bilhão de tutoriais e tutoriais explicando como obter assincronia no JavaScript. No entanto, muitos deles simplesmente explicaram como converter funções de retorno de chamada em promessas ou promessas em assíncrono / espera. Para muitos, isso é provavelmente mais que suficiente para se dar bem com eles e começar a usá-los em seu código.
No entanto, se você, como eu, realmente deseja entender a programação assíncrona (e não apenas a sintaxe do JavaScript!), Pode concordar comigo que há uma escassez de materiais explicando a programação assíncrona do zero.
O que significa assíncrono?

Como regra, ao fazer esta pergunta, você pode ouvir algo do seguinte:
- Existem vários threads que executam código ao mesmo tempo.
- Mais de um pedaço de código é executado por vez.
- Isso é simultaneidade.
Até certo ponto, todas as opções estão corretas. Mas, em vez de fornecer uma definição técnica que você provavelmente esquecerá em breve, darei
um exemplo que até uma criança pode entender .
Exemplo de vida

Imagine que você está cozinhando sopa de legumes. Para uma analogia boa e simples, suponha que uma sopa de legumes consista apenas em cebola e cenoura. A receita para essa sopa pode ser a seguinte:
- Pique as cenouras.
- Pique a cebola.
- Adicione água à panela, ligue o fogão e aguarde até ferver.
- Adicione as cenouras à panela e deixe por 5 minutos.
- Adicione as cebolas à panela e cozinhe por mais 10 minutos.
Essas instruções são simples e compreensíveis, mas se um de vocês, lendo isso, realmente sabe cozinhar, pode dizer que essa não é a maneira mais eficaz de cozinhar. E você estará certo, é por isso:
- As etapas 3, 4 e 5 não exigem que você, como chef, faça nada, exceto observar o processo e acompanhar o tempo.
- As etapas 1 e 2 exigem que você faça algo ativamente.
Portanto, a receita para um cozinheiro mais experiente pode ser a seguinte:
- Comece a ferver uma panela de água.
- Enquanto espera a panela ferver, comece a cortar as cenouras.
- Quando você terminar de cortar as cenouras, a água deve ferver, então adicione as cenouras.
- Enquanto as cenouras são cozidas em uma panela, pique a cebola.
- Adicione as cebolas e cozinhe por mais 10 minutos.
Apesar de todas as ações permanecerem as mesmas, você tem o direito de esperar que essa opção seja muito mais rápida e eficiente. Esse é exatamente o princípio da programação assíncrona:
você nunca quer ficar sentado, apenas esperando por algo, enquanto pode gastar seu tempo com outras coisas úteis.
Todos sabemos que, na programação,
esperar por algo acontece com bastante frequência - esteja esperando uma resposta HTTP de um servidor ou uma ação de um usuário ou outra coisa. Mas os ciclos de execução do seu processador são preciosos e devem
sempre ser usados ativamente, fazendo algo e não esperando: isso resulta em
programação assíncrona .
Agora vamos ao JavaScript, ok?
Então, seguindo o mesmo exemplo de sopa de legumes, escreverei algumas funções para representar as etapas da receita descrita acima.
Primeiro, vamos escrever funções síncronas que representam tarefas que não levam tempo. Essas são as boas e antigas funções JavaScript, mas observe que descrevi as
chopOnions
e
chopOnions
como tarefas que exigem trabalho ativo (e tempo), permitindo que eles façam alguns cálculos longos. O código completo está disponível no final do artigo [1].
function chopCarrots() { console.log(" !"); } function chopOnions() { console.log(" !"); } function addOnions() { console.log(" !"); } function addCarrots() { console.log(" !"); }
Antes de passar para as funções assíncronas, primeiro explicarei rapidamente como o sistema de tipo JavaScript lida com assincronia: basicamente
todos os resultados (incluindo erros) de operações assíncronas devem estar envolvidos nas promessas .
Para que uma função retorne uma promessa, você pode:
- devolver explicitamente a promessa, ou seja,
return new Promise(…)
; - retornar implicitamente uma promessa - adicione a
async
à declaração da função, ou seja, async function foo()
; - use as duas opções .
Existe um excelente artigo [2], que fala sobre a diferença entre funções assíncronas e funções que retornam uma promessa. Portanto, no meu artigo, não vou me debruçar sobre esse tópico, a principal coisa a lembrar: você
sempre deve usar a
async
em funções assíncronas.
Portanto, nossas funções assíncronas, representando as etapas 3-5 da preparação da sopa de legumes, são as seguintes:
async function letPotKeepBoiling(time) { return;
Mais uma vez, excluí os detalhes da implementação para não nos distrairmos, mas eles são publicados no final do artigo [1].
É importante saber que, para aguardar o resultado da promessa, para que mais tarde você possa fazer algo com ela, basta usar a palavra-chave wait:
async function asyncFunction() { } result = await asyncFunction();
Então, agora só precisamos juntar tudo:
function makeSoup() { const pot = boilPot(); chopCarrots(); chopOnions(); await pot; addCarrots(); await letPotKeepBoiling(5); addOnions(); await letPotKeepBoiling(10); console.log(" !"); } makeSoup();
Mas espera! Isso não funciona! Você verá o
SyntaxError: await is only valid in async functions
erro de
SyntaxError: await is only valid in async functions
. Porque Como se você não declarar uma função usando a
async
, por padrão, o JavaScript a definirá como uma função síncrona - e síncrona significa não esperar! [3] Isso também significa que você não pode usar
await
fora de uma função.
Portanto, simplesmente adicionamos a
async
à função
makeSoup
:
async function makeSoup() { const pot = boilPot(); chopCarrots(); chopOnions(); await pot; addCarrots(); await letPotKeepBoiling(5); addOnions(); await letPotKeepBoiling(10); console.log(" !"); } makeSoup();
E pronto! Observe que na segunda linha, eu chamo a função assíncrona
boilPot
sem a palavra-chave
boilPot
, porque não queremos esperar a panela ferver antes de começarmos a cortar as cenouras. Esperamos apenas a promessa de
pot
na quinta linha antes de precisarmos colocar as cenouras na panela, porque não queremos fazer isso antes que a água ferva.
O que acontece durante as chamadas em
await
? Bem, nada ... mais ou menos ...
No contexto da função
makeSoup
você pode simplesmente pensar nisso como se esperasse que algo acontecesse (ou um resultado que acabaria sendo retornado).
Mas lembre-se:
você (como o seu processador) nunca vai querer ficar sentado e esperar por algo, enquanto pode gastar seu tempo com outras coisas .
Portanto, em vez de apenas cozinhar sopa, poderíamos cozinhar outra coisa em paralelo:
makeSoup(); makePasta();
Enquanto aguardamos o
letPotKeepBoiling
, podemos, por exemplo, cozinhar macarrão.
Está vendo? A sintaxe assíncrona / aguardar é realmente muito fácil de usar, se você entende, concorda?
E as promessas abertas?
Bem, se você insistir, voltarei ao uso de promessas explícitas (
aprox. Transl.: Por promessas explícitas, o autor implica diretamente a sintaxe das promessas, e por promessas implícitas a sintaxe assíncrona / aguardada, porque retorna a promessa implicitamente - não há necessidade de escrever return new Promise(…)
). Lembre-se de que os métodos async / waitit
são baseados nas próprias promessas e, portanto, ambas as opções são totalmente compatíveis .
Promessas explícitas, na minha opinião, estão entre os retornos de chamada à moda antiga e a nova sintaxe assíncrona / sexual de espera. Como alternativa, você também pode pensar na sintaxe sexual assíncrona / aguardada como nada além de promessas implícitas. No final, a construção assíncrona / aguardada veio após as promessas, que por sua vez vieram após as funções de retorno de chamada.
Use nossa máquina do tempo para ir para o inferno de retorno de chamada [4]:
function callbackHell() { boilPot( () => { addCarrots(); letPotKeepBoiling(() => { addOnions(); letPotKeepBoiling(() => { console.log(" !"); }, 1000); }, 5000); }, 5000, chopCarrots(), chopOnions() ); }
Não vou mentir, escrevi esse exemplo rapidamente quando estava trabalhando neste artigo, e levou muito mais tempo do que gostaria de admitir. Muitos de vocês podem nem saber o que está acontecendo aqui.
Meu querido amigo, todas essas funções de retorno de chamada não são terríveis? Que seja uma lição nunca mais usar funções de retorno de chamada ...E, como prometido, o mesmo exemplo com promessas explícitas:
function makeSoup() { return Promise.all([ new Promise((reject, resolve) => { chopCarrots(); chopOnions(); resolve(); }), boilPot() ]) .then(() => { addCarrots(); return letPotKeepBoiling(5); }) .then(() => { addOnions(); return letPotKeepBoiling(10); }) .then(() => { console.log(" !"); }); }
Como você pode ver, as promessas ainda são semelhantes às funções de retorno de chamada.
Não vou entrar em detalhes, mas o mais importante:
.then
é um método de promessa que pega seu resultado e o passa para a função de argumento (essencialmente, para uma função de retorno de chamada ...)- Você nunca pode usar o resultado de uma promessa fora do contexto de
.then
. Em essência, .then é como um bloco assíncrono que espera um resultado e o passa para uma função de retorno de chamada. - Além do método
.then
, há outro método nas .catch
- .catch
. É necessário lidar com erros nas promessas. Mas não vou entrar em detalhes, porque já existem um bilhão de artigos e tutoriais sobre esse tópico.
Conclusão
Espero que você tenha alguma idéia de promessas e programação assíncrona neste artigo, ou talvez pelo menos tenha aprendido um bom exemplo da vida para explicar isso a outra pessoa.
Então, de que maneira você usa: promessas ou assíncrono / espera?A resposta é totalmente sua: e eu diria que combiná-las não é tão ruim, pois as duas abordagens são completamente compatíveis entre si.
No entanto, eu pessoalmente estou 100% no campo de async / waiting, porque para mim o código é muito mais compreensível e reflete melhor a verdadeira multitarefa da programação assíncrona.
[1] : o código fonte completo está disponível
aqui .
[2] : Artigo original
“Função assíncrona vs. uma função que retorna uma promessa " , tradução do artigo
" A diferença entre uma função assíncrona e uma função que retorna uma promessa " .
[3] : Você pode argumentar que o JavaScript provavelmente pode determinar o tipo de assíncrona / espera do corpo das funções e verificar recursivamente, mas o JavaScript não foi projetado para cuidar da segurança do tipo estático no tempo de compilação, sem mencionar que é muito mais conveniente para os desenvolvedores verem explicitamente o tipo de função.
[4] : Eu escrevi funções "assíncronas", assumindo que elas funcionem sob a mesma interface que
setTimeout
. Observe que os retornos de chamada não são compatíveis com promessas e vice-versa.