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.
function getValues() { return Promise.resolve([1,2,3,4]); } getValues().then(function(values) { console.log(values); });
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.
function asyncOperation(value) { return Promise.resolve(value + 1); } function foo() { return getValues().then(function(values) { var operations = values.map(function(value) { return asyncOperation(value).then(function(newValue) { console.log(newValue); return newValue; }); }); return Promise.all(operations); }).catch(function(err) { console.log('Tivemos um ', err); }); }
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:
function foo() { return getValues().then(function(values) { var operations = values.map(asyncOperation); return Promise.all(operations).then(function(newValues) { newValues.forEach(function(newValue) { console.log(newValue); }); return newValues; }); }).catch(function(err) { console.log('Tivemos um ', err); }); }
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.
function foo() { var newValues = []; return getValues().then(function(values) { return values.reduce(function(previousOperation, value) { return previousOperation.then(function() { return asyncOperation(value); }).then(function(newValue) { console.log(newValue); newValues.push(newValue); }); }, Promise.resolve()).then(function() { return newValues; }); }).catch(function(err) { console.log('Tivemos um ', err); }); }
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 return
s aninhados, dessa forma:
function foo() { return getValues().then(function(values) { return values.reduce(function(previousOperation, value) { return previousOperation.then(function(newValues) { return asyncOperation(value).then(function(newValue) { console.log(newValue); newValues.push(newValue); return newValues; }); }); }, Promise.resolve([])); }).catch(function(err) { console.log('Tivemos um ', err); }); }
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:
async function foo() { if( Math.round(Math.random()) ) return 'Sucesso!'; else throw 'Falhou!'; } // É equivalente a... function foo() { if( Math.round(Math.random()) ) return Promise.resolve('Sucesso!'); else return Promise.reject('Falhou!'); }
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
:
async function getValues() { return [1,2,3,4]; } async function asyncOperation(value) { return value + 1; }
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:
function promisingOperation() { return new Promise(function(resolve, reject) { setTimeout(function() { if( Math.round(Math.random()) ) resolve('Sucesso!'); else reject('Falhou!'); }, 1000); } } async function foo() { var message = await promisingOperation(); console.log(message); }
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:
async function foo() { try { var message = await promisingOperation(); console.log(message); } catch (e) { console.log('Nós falhamos:', e); } }
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:
async function() { console.log(await getValues()); }(); // O parênteses "()" extra executa, imediatamente, a função
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
.
async function foo() { try { var values = await getValues(); var newValues = values.map(async function(value) { var newValue = await asyncOperation(value); console.log(newValue); return newValue; }); return await* newValues; } catch (err) { console.log('Tivemos um ', err); } }
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.
async function foo() { try { var values = await getValues(); var newValues = await Promise.all(values.map(asyncOperation)); newValues.forEach(function(value) { console.log(value); }); return newValues; } catch (err) { console.log('Tivemos um ', err); } }
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.
async function foo() { try { var values = await getValues(); return await values.reduce(async function(values, value) { values = await values; value = await asyncOperation(value); console.log(value); values.push(value); return values; }, []); } catch (err) { console.log('Tivemos um ', err); } }
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:
npm install -g traceur
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:
traceur main.js --experimental --out compiled.js
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.
- 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 comoindex.js
dentro do diretório do módulo. - Em seu arquivo HTML, adicione uma tag
script
para buscar o script do Traceur Runtime. - Adicione outra tag
script
abaixo do Traceur Runtime para buscar o arquivocompiled.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!
Subscribe below and we’ll send you a weekly email summary of all new Code tutorials. Never miss out on learning about the next big thing.
Update me weeklyEnvato Tuts+ tutorials are translated into other languages by our community members—you can be involved too!
Translate this post