Desenvolvimento

25 ago, 2014

Sincronizando JavaScript Assíncrono com o ES7

Publicidade

A versão 5 do ECMAScript (ES) é a versão completa do JavaScript mais recente disponível no mercado. Está implementada em todos os principais navegadores, mas está em obras e promete um mundo muito melhor para os desenvolvedores JavaScript, com características tais como uma sintaxe simples de classes, funções de seta, promises nativas e afins. Mas não para por aí: já estamos olhando além, enquanto o ES6 ainda está no forno. Particularmente, estou muito animado com as funções assíncronas.

A situação atual, o ES6 e o futuro

Antes de mergulharmos nas funções assíncronas do ES7, quero desenvolver meu raciocínio mostrando como atualmente se implementam as coisas, como isso vai mudar no ES6 e depois olhar para como as funções assíncronas simplesmente melhoram tudo. Em primeiro lugar, vamos dar uma olhada nas promises. Elas são um padrão estabelecido na especificação Promises/A. Atualmente, há dezenas de bibliotecas que fornecem suas próprias implementações dessa especificação, e a maioria ainda oferece alguns outros recursos adicionais. É bom que tenhamos um padrão e que a funcionalidade mais importante seja implementada da mesma maneira em todo lugar, mas ainda há uma série de inconsistências entre as bibliotecas. Seria bom resolver isso com uma única implementação… e nós resolvemos! O ES6 traz sua própria implementação de promises que deve dominar a situação e se tornar o jeito de fazer as coisas de fato. Ainda não sei direito o que acho da sintaxe, mas isso não é um grande problema.

Promises são ótimas, mas ainda gostamos de escrever nosso código de forma síncrona. Se usamos callbacks “para tudo quanto é lado” ou as substituímos por promises, ainda assim elas são mais difíceis de ler do que código síncrono. Bom, outra característica ótima do ES6 chegou para nos salvar: os geradores. Jmar777 fala sobre os geradores, dando-nos um rápido resumo, e depois nos conta como ele criou uma biblioteca que aproveita os geradores para forçar o código a simplesmente esperar até que a operação assíncrona seja concluída antes de ir para a próxima linha. Isso funciona muito bem e, por enquanto, pode ser extremamente útil.

Aqui está um exemplo (adaptado da página async functions proposal) de uso exclusivo das promises vs uso de promises + Suspend (biblioteca do gerador do jmar777):

// With Pure Promises
function chainAnimationsPromise(elem, animations) {
    var ret = null;
    var p = currentPromise;
    for(var anim in animations) {
        p = p.then(function(val) {
            ret = val;
            return anim(elem);
        })
    }
    return p.catch(function(e) {
        /* ignore and keep going */
    }).then(function() {
        return ret;
    });
}
 
// With Suspend
function chainAnimationsGenerator(elem, animations) {
    return suspend(function*() {
        var ret = null;
        try {
            for(var anim of animations) {
                ret = yield anim(elem);
            }
        } catch(e) { /* ignore and keep going */ }
        return ret;
    });
}

A mágica aqui é a linha suspend(function*()…)… e a palavra yield. Fiquei de boca aberta na primeira vez em que vi que era possível fazer isso.

O presente do ES7 para os desenvolvedores web

Usar geradores funciona, mas é meio chato. Os geradores não foram projetados originalmente para isso, mesmo servindo bem para esse propósito. Em vez disso, o JavaScript vai receber um modo nativo de interromper a execução do código enquanto esperamos uma operação assíncrona terminar. A gente faz isso usando a palavra chave await dentro de uma função async:

// With Asynchronous Functions and `await`
async function chainAnimationsAsync(elem, animations) {
    var ret = null;
    try {
        for(var anim of animations) {
            ret = await anim(elem);
        }
    } catch(e) { /* ignore and keep going */ }
    return ret;
}

Você deve usar async na função para o await funcionar. Além disso, observe que quando você usa await, se a promise for resolvida, ela vai avaliar o valor pelo qual a promese foi resolvida, então você pode usar uma atribuição simples como fizemos no exemplo. Se a promise for rejeitada, vai gerar um erro, o que significa que podemos encontrar rejeições nos blocos try e catch. Usar a palavra-chave await deve funcionar com qualquer promise, não apenas as devolvidas de outra função assíncrona ou de uma promise nativa do ES6.

Quando iniciamos a declaração de função com async, ela vai retornar uma promise sem você nem precisar tocar na API de promise. Para resolvê-la, simplesmente use um valor de retorno da função (ou não retorne nenhum valor, se você quiser que ela se resolva sem um valor). Se você quer rejeitar a promise, simplesmente escreva throw em seu valor de rejeição.

Se você for como eu, deve estar pensando que isso é uma maravilha, mas não é muito útil porque esse recurso ainda não está disponível. Bom, é verdade, mas o traceur compiler na verdade já suporta compilar esse recurso para o ES5. Então, se você acha que acrescentar uma etapa de compilação vai valer o tempo que você vai economizar, com certeza é bom dar uma olhada nisso.

Notas

Você pode ter observado que o exemplo da função assíncrona é muito parecido com o exemplo de Suspend, a não ser pelo fato de que não precisamos requisitar uma biblioteca para ela. Não precisamos da função final suspend, então acrescentamos a palavra-chave async na frente da declaração da função e substituímos yield por await. Há um motivo para essa semelhança. Para citar a página das especificações:

“As funções assíncronas são como uma camada fina de açúcar sobre os geradores e uma função spawn que converte geradores em objetos de promise”.

Em outras palavras, se por um lado considerei o uso de geradores uma chatice, eles ainda estão sendo usados nos bastidores. Nós apenas os substituímos por uma sintaxe mais limpa e mais clara, projetada especificamente para promises. A razão pela qual await só funciona dentro de uma função async é que a palavra-chave async é o sinal dado ao tradutor para substituí-la por uma função spawn/suspend e trocar toda as palavras-chave await por yield.

Lembre-se também de que essa especificação está em fases muito iniciais, portanto as coisas podem mudar substancialmente – se bem que não vejo o que poderiam mudar, a não ser, talvez, as palavras-chave.

Finalmente, outro ótimo artigo sobre isso é o de Jake Archibald sobre as funções async do ES7. Vale a pena conferir.

Conclusão

Eu ficava empolgado com o ES6, mas agora estou mais empolgado ainda com o ES7. Promises eram para ser a solução do problema de operações assíncronas, mas na realidade só resolveram um pequeno subconjunto daquilo que precisávamos. Acho que as funções assíncronas do ES7 levam promises a um patamar mais alto e simplificam de fato a programação assíncrona.

***

Artigo traduzido pela Redação iMasters com autorização do autor. Publicado originalmente em http://www.joezimjs.com/javascript/synchronizing-asynchronous-javascript-es7/