Advertisement
  1. Code
  2. JavaScript

O Básico Sobre as Funções Assíncronas do ES7

by
Difficulty:IntermediateLength:LongLanguages:

Portuguese (Português) translation by Erick Patrick (you can also view the original English article)

Se você tem acompanhado o mundo do JavaScript, provavelmente já ouviu falar das promessas (promises). Existem ótimos tutoriais na internet, caso queira aprender sobre elas, mas não as explicarei aqui; este artigo assumirá que você possui este conhecimento. 

As promessas são tidas como o futuro da programação assíncrona no JavaScript. Elas são ótimas e ajudam a resolver vários dos problemas que surgem com programação assíncrona, mas só em parte. Na verdade, as promessas são a base do futuro da programação assíncrona no JavaScript. Em um mundo ideal, elas serão colocadas em segundo plano e seremos capazes de criar códigos assíncronos como se eles fossem síncronos.

No ECMAScript 7, isso será muito mais que um sonho fantástico: Isso será realidade e mostrarei a você essa realidade—chamada de funções assíncronas—agora mesmo. Por que estamos falando disso agora? Afinal de contas, o ES6 nem foi completamente finalizado, então, quem é que sabe quanto tempo que levará para vermos o ES7. A verdade é que já podemos usar essa tecnologia agora. No final deste artigo, mostrarei como fazê-lo.

A Situação Atual

Antes de começarmos a demonstrar como usar funções assíncronas, gostaria de passar por alguns exemplos que fazem uso de promessas (usando as promessas do ES6). Depois, converterei esses exemplos para que façam uso das funções assíncronas, de modo que você veja o quão diferente fica.

Exemplos

Para nosso primeiro exemplo, faremos algo bem simples: invocaremos uma função assíncrona e registraremos o valor retornado por ela.

Agora que temos o exemplo básico pronto, é hora de tentarmos algo mais complicado. Usarei e modificarei alguns exemplos de uma publicação do meu blog pessoal, onde falo sobre alguns padrões para uso de promessas em vários e diferentes cenários. Cada um dos exemplos retorna, assincronamente, um vetor de valores, realizando operações assíncronas que transformam cada um dos valores do vetor, registra cada um dos valores e, finalmente, retorna o vetor com os novos valores gerados.

Primeiro, veremos um exemplo que executará várias operações assíncronas em paralelo e, assim que forem terminando, responderá imediatamente a cada uma delas, independente da ordem que forem finalizando. A função getValues é a mesma do exemplo anterior. A função asyncOperation também será reutilizada nos exemplos posteriores.

Podemos ver exatamente a mesma coisa, porém, garantindo que o registro dos valores aconteça na ordem dos elementos do vetor. Em outras palavras, no próximo exemplo, faremos o trabalho assíncrono em paralelo, mas o trabalho síncrono será sequencial:

Nosso último exemplo demonstrará um padrão onde esperamos por uma operação assíncrona anterior finalizar antes de começarmos a posterior. Não temos nada executando em paralelo neste exemplo; tudo será sequencial.

Mesmo com a capacidade de redução de aninhamento de callbacks das promessas, isso não ajudará muito. Executar um número desconhecido de chamadas assíncronas sequenciais será uma bagunça, não importa o que faça. É, especialmente, apavorante ver todas essas palavras-chave return aninhadas. Se passássemos o vetor newValues pelas promessas da função callback reduce, ao invés de torná-la global para toda a função foo, precisaríamos ajustar o código para termos ainda mais returns aninhados, dessa forma:

Não concorda que precisamos ajustar isso? Vejamos uma solução.

Funções Assíncronas ao Resgate

Mesmo com promessas, programação assíncrona não é simples e não necessariamente fluirá bem de A à Z. Programação síncrona é muito mais simples e é criada e lida de forma muito mais natural. A especificação das Funções Assíncronas busca uma forma (usando os geradores do ES6, por trás dos panos) de criar seu código como se fosse síncrono.

Como Os Utilizamos?

A primeira coisa que precisamos fazer é prefixar nossas funções com a palavra-chave async. Sem essa palavra-chave em seu devido lugar, não poderemos usar a super importante palavra-chave await dentro da função, a qual explicaremos daqui a pouco. 

A palavra-chave async não apenas nos permite usar a palavra-chave await, como também garante que a função retornará um objeto do tipo Promise. Dentro da função assíncrona, toda vez que você retornar algum valor, a função, na verdade, retornará um objeto Promise que será resolvido com aquele valor. A forma de rejeição é lançar um erro, no qual o valor da rejeição será um objeto de erro. Eis um exemplo simples:

Ainda nem chegamos à melhor parte e nosso código já parece mais síncrono porque pudemos parar de lidar, explicitamente, com o objeto Promise. Podemos usar qualquer função e fazê-la retornar um objeto Promise, apenas adicionando a palavra-chave async antes dela. 

Continuemos e convertamos nossas funções getValues e asyncOperation:

Fácil! Agora, vejamos a melhor parte de todas: a palavra-chave await. Dentro de sua função assíncrona, toda vez que realizar uma operação que retorne uma promessa, você pode colocar a palavra-chave await na frente dela, e ela parará a execução do resto da função até que a promessa retornada seja resolvida ou retornada. Nesse ponto, await promisingOperation() avaliará o valor resolvido ou rejeitado. Por exemplo:

Ao invocar foo, ou ele esperará até que promisingOperation resolva e registrará a mensagem "Sucesso!" ou esperará que promisingOperation rejeite, nesse caso a rejeição será retornada, e foo também rejeitará com "Falhou!". Como foo não retorna algo, ela resolverá undefined, assumindo que promisingOperation foi bem sucedida.

Só há mais uma pergunta faltando: como resolvemos falhas? A resposta a essa pergunta é bem simples: Tudo que precisamos fazer é envolvê-la em um bloco try...catch. Se uma das operações forem rejeitadas, com o catch poderemos manipulá-la:

Agora que sabemos do básico, peguemos nossos exemplos de promessas anteriores e os convertamos em funções assíncronas.

Exemplos

O primeiro exemplo acima, criou a função getValues e a usou. Nós já recriamos getValues, só precisamos recriar o código que faz uso dela. Há um possível problema em relação a funções assíncronas que pode surgir aqui: É requerido que o código esteja em função. O Exemplo anterior estava no escopo global (pelas circunstâncias, era o que se podia dizer), mas precisamos envolver nosso código assíncrono em uma função assíncrona para fazê-lo funcionar:

Mesmo envolvendo o código em uma função, ainda é fácil lê-lo e tem menos bytes (se você remover o comentário). Nosso próximo exemplo, se você se lembrar bem, realiza tudo em paralelo. Ele é um pouco complicado, porque temos uma função interna que precisa retornar uma promessa. Se usarmos a palavra-chave await dentro da função interna, ela também precisará ser prefixada com async.

Você talvez tenha percebido o asterisco atrelado à última declaração da palavra-chave await. Esse ponto ainda está em debate, mas, em essência, parece que await* irá auto envolver a expressão à sua direita em Promise.all. Hoje, porém, a ferramenta que veremos mais adiante, não suporta await*, assim, deveremos convertê-lo em await Promise.all(newValues); como faremos no próximo exemplo.

O próximo exemplo executará as chamadas a asyncOperation em paralelo, mas trará todas juntas e as retornará em sequência.

Amo isso. É extremamente limpo. Se removêssemos a palavra-chave await e a async, removeríamos o invólucro Promise.all, e tornaríamos getValues e asyncOperation em síncronas, então, esse código ainda funcionará da mesma forma, exceto que ele seria síncrono. E é exatamente o que estamos buscando fazer.

Nosso exemplo final terá tudo executando sequencialmente, claro. Nenhuma operação assíncrona é realizada até que a anterior tenha finalizado.

Novamente, tornamos a função interna em uma função assíncrona, usando async. Há uma peculiaridade nesse código. Passei [] como o valor memorizador de reduce, mas, então, usei await na função. O valor à direita de await não é obrigado ser uma promessa. Ele pode ser qualquer valor e, caso não seja uma promessa, não esperará por ele; ele será executado sincronamente. Claro, após a primeira execução da função callback, na verdade, trabalharemos com uma promessa.

Esse exemplo é bem parecido com o primeiro exemplo, exceto que usamos a função reduce ao invés de map para que possamos esperar (await) a operação anterior. E por usarmos reduce para construir um vetor (não algo que você normalmente faria, especialmente se estiver construindo um vetor do mesmo tamanho do vetor original), precisamos construir o vetor dentro da função callback de reduce.

Usando Funções Assíncronas Hoje

Agora que você entendeu a simplicidade e grandiosidade das funções assíncronas, você deve estar com lágrimas nos olhos, assim como fiquei da primeira vez que as vi. Não estava chorando de felicidade (embora quase o tenha feito); não... estava chorando porque o ES7 não estará disponível antes deu morrer! Pelo menos foi assim que eu me senti. Até que descobri o Traceur.

O Traceur é escrito e mantido pelo Google. Ele compilador fonte-a-fonte (transpiler), que converte códigos ES6 em ES5. E isso não ajuda! Quer dizer, não ajudaria, já que eles também implementaram o suporte a funções assíncronas. Ainda é uma funcionalidade experimental, significando que você precisa avisar ao compilador que você usará essa funcionalidade e que você quer testar seu código minuciosamente, para garantir que não há quaisquer problemas na compilação.

Usar um compilador como o Traceur significa que você terá um código levemente maior e feio, enviado para o cliente. E não é isso que você quer. Mas, se usar mapeamento de fonte (source maps), ele eliminará a maior parte dos problemas relacionados ao desenvolvimento. Você lerá, escreverá e depurará código ES6/7 limpo e claro, ao invés de ler e escrever e depurar uma bagunça de código que precisa contornar as limitações atuais da linguagem. 

Claro, o tamanho do código também será maior que o que você teria escrito diretamente com código em ES5 (provavelmente), então você tem de encontrar um equilíbrio entre código manutenível e código performático, mas esse equilíbrio é algo que você precisa encontrar mesmo sem usar um compilador fonte-a-fonte.

Usando o Traceur

O Traceur é um utilitário de linha de comando que pode ser instalado através do NPM:

No geral, o Traceur é bem simples de usar, mas algumas de suas opções podem ser um pouco confusa e podem requerer alguma experimentação. Você pode ver uma lista de opções para maiores detalhes. A que estamos interessados é a opção --experimental.

Você precisar usar essa opção para habilitar funcionalidades experimentais, que é como faremos as funções assíncronas funcionarem. Assim que tiver um arquivo JavaScript (main.js, nesse caso) com código assíncrono incluso, você pode apenas compilá-lo, dessa forma:

Você também pode apenas executar o código omitindo --out compiled.js. Você não verá muita coisa a não ser que o código tenha expressões console.log (ou outros retornos de linha de comando), mas, no mínimo, você pode verificar erros. Você, com certeza, quererá executá-lo em um navegador. Se for o caso, é preciso mais alguns passos a serem tomados.

  1. Baixe o script traceur-runtime.js. Há inúmeras maneiras de obtê-lo, mas a mais fácil é através do NPM: npm install traceur-runtime. O arquivo estará disponível como index.js dentro do diretório do módulo.
  2. Em seu arquivo HTML, adicione uma tag script para buscar o script do Traceur Runtime.
  3. Adicione outra tag script abaixo do Traceur Runtime para buscar o arquivo compiled.js.

Após isso, seu código já deve estar pronto e funcionando!

Automatizando a Compilação do Traceur

Além de usar a ferramenta de linha de comando do Traceur, você também pode automatizar a compilação para que não precise retornar à linha de comando e reexecutar o compilador. O Grunt e o Gulp, que são executadores de tarefas automatizadas, possuem plugins que permitem você automatizar a compilação do Traceur: grunt-traceur e gulp-traceur, respectivamente.

Cada um dos executadores pode ser configurado para vigiar seu sistema de arquivos e recompilar o código no instante que você salvar alterações nos seus arquivos JavaScript. Para aprender como usar o Grunt ou o Gulp, veja as seções "Getting Started" das respectivas documentações.

Conclusão

As funções assíncronas do ES7 oferecem aos desenvolvedores uma forma de real de evitar o inferno de funções callbacks, de um jeito que as promessas jamais puderam fazer. Essa nova funcionalidade permite-nos criar códigos assíncronos e, embora o ES6 ainda espere pelo seu lançamento completo, já podemos usar funções assíncronas hoje, através da compilação fonte-a-fonte. O que você está esperando? Vá criar códigos extraordinários!

Seja o primeiro a saber sobre novas traduções–siga @tutsplus_pt no Twitter!

Advertisement
Advertisement
Advertisement
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.