Iteradores e Geradores Assíncronos em JavaScript
Aprenda iteradores e geradores assíncronos em JavaScript: Symbol.asyncIterator, async function*, for await...of e um exemplo de paginação lazy com código executável.
A programação assíncrona é um pilar fundamental do desenvolvimento moderno em JavaScript, permitindo que desenvolvedores escrevam código não bloqueante e concorrente, capaz de lidar eficientemente com tarefas como requisições de rede, I/O de arquivos e temporizadores. Este guia aborda iteradores assíncronos e geradores assíncronos, dois recursos introduzidos no ECMAScript 2018 que permitem iterar sobre dados que chegam ao longo do tempo — um fragmento por requisição de rede, um evento por ação do usuário — sem bloquear o restante do programa.
Esta página pressupõe que você está familiarizado com iteráveis, geradores e promises regulares. Se algum desses for novidade, leia-os primeiro.
Entendendo Iteradores Assíncronos
O que são Iteradores Assíncronos?
Iteradores assíncronos são um tipo especial de iterador projetado para lidar com fluxos de dados assíncronos. Ao contrário dos iteradores tradicionais, que operam de forma síncrona, os iteradores assíncronos permitem que os desenvolvedores iterem sobre sequências de valores assíncronos, como promises ou streams, de maneira não bloqueante.
Tecnicamente, um object é considerado um iterável assíncrono se implementar o método Symbol.asyncIterator, que retorna um object iterador assíncrono. Veja um exemplo prático de implementação manual dessa interface em um object personalizado:
Iteradores Síncronos vs. Iteradores Assíncronos
A diferença entre um iterável regular e um iterável assíncrono se resume a três aspectos: o nome do método, o tipo de retorno de next() e o loop utilizado para consumi-lo.
| Iterável síncrono | Iterável assíncrono | |
|---|---|---|
| Método | Symbol.iterator | Symbol.asyncIterator |
next() retorna | { value, done } | uma Promise de { value, done } |
| Loop de consumo | for...of | for await...of |
| Sintaxe de gerador | function* | async function* |
Como next() retorna uma Promise no caso assíncrono, cada etapa do loop pode aguardar uma operação assíncrona — um fetch, um temporizador, uma leitura de banco de dados — antes que o próximo valor seja produzido. Um for...of simples não consegue fazer isso: ele espera que value/done estejam disponíveis imediatamente. Usar for await...of em um iterável apenas síncrono ainda funciona (o motor envolve os valores em promises resolvidas), mas usar um for...of síncrono em um iterável assíncrono não funciona — você estaria apenas iterando sobre objetos Promise pendentes.
Como Usar Iteradores Assíncronos
Para aproveitar os iteradores assíncronos no seu código JavaScript, você primeiro precisa entender seus conceitos e sintaxe fundamentais. Vamos explorar um exemplo simples para demonstrar como os iteradores assíncronos funcionam na prática:
Neste exemplo, definimos uma função geradora assíncrona generateNumbers() que produz uma sequência de números de forma assíncrona. Em seguida, criamos um iterável assíncrono a partir da função geradora e usamos um loop for await...of para iterar sobre os valores produzidos pelo iterador assíncrono.
Nota: Quando você faz yield de um valor simples dentro de uma async function*, o método next() do iterador retorna automaticamente uma Promise que resolve para { value: <seu valor>, done: false }. Você só precisa fazer yield explícito de uma Promise se quiser que o consumidor receba um object Promise em vez do valor resolvido.
Aplicações do Mundo Real de Iteradores Assíncronos
Os iteradores assíncronos são amplamente utilizados em cenários que envolvem processamento de dados assíncronos, como busca de dados de APIs externas, leitura de streams ou manipulação de eventos assíncronos. Sua versatilidade e eficiência os tornam ferramentas indispensáveis para desenvolvedores JavaScript modernos que buscam escrever aplicações escaláveis e responsivas.
Explorando Geradores JavaScript
Introdução aos Geradores
Geradores são um recurso poderoso introduzido no ECMAScript 2015 que permite a criação de sequências iteráveis com lógica de iteração personalizada. Ao contrário das funções tradicionais, que executam até o fim quando invocadas, os geradores podem pausar e retomar sua execução, permitindo a avaliação preguiçosa (lazy) de valores.
É importante distinguir entre geradores padrão e geradores assíncronos:
- Geradores Padrão (
function*): Produzem valores de forma síncrona. - Geradores Assíncronos (
async function*): Retornam uma Promise de cada chamadanext(), permitindo que o consumidor aguarde cada valor usandofor await...of.
Usando Geradores para Programação Assíncrona
Um dos casos de uso mais atraentes para geradores é a programação assíncrona. Combinando geradores com promises, os desenvolvedores podem criar fluxos de trabalho assíncronos que são ao mesmo tempo elegantes e fáceis de compreender. Veja um exemplo moderno de uso de um gerador assíncrono para buscar e produzir dados de um servidor remoto:
Neste exemplo, definimos uma função geradora assíncrona fetchTodos() que busca dados de forma assíncrona de uma API remota usando a função fetch(). Ao usar await dentro do gerador e produzir itens individualmente, podemos transmitir os resultados diretamente para um loop for await...of sem chamadas manuais a .next() ou encadeamento de promises.
Busca Paginada com um Gerador Assíncrono
O padrão que faz os geradores assíncronos se destacarem é a paginação lazy. Muitas APIs retornam resultados em páginas e esperam que você continue solicitando a próxima página até que não haja mais. Um gerador assíncrono pode ocultar todo esse controle: ele busca uma página, produz seus itens um a um e só solicita a próxima página quando o consumidor pede por mais. O chamador pode parar mais cedo — por exemplo, após encontrar o que precisa — e nenhuma requisição de rede adicional é feita.
Observe o break: como o gerador é lazy, sair do loop após 25 itens significa que o gerador nunca solicita a página 3. É isso que diferencia um gerador assíncrono de buscar tudo antecipadamente em um array — você paga apenas pelos dados que realmente utiliza.
Padrões Avançados de Geradores
Os geradores oferecem uma infinidade de padrões e técnicas avançadas para resolver problemas de programação complexos. Aqui estão alguns exemplos que demonstram sua versatilidade:
- Execução Paralela: Ao iniciar múltiplos geradores e gerenciar suas promises de forma concorrente, você pode executar várias tarefas assíncronas simultaneamente.
- Tratamento de Erros: Use blocos
try-catchdentro dos geradores para lidar graciosamente com promises rejeitadas produzidas durante o processo de iteração. - Pipelines de Dados: Construa pipelines de processamento de dados encadeando geradores, onde a saída de um gerador serve como entrada para o próximo.
Conclusão
Em conclusão, iteradores assíncronos e geradores são ferramentas indispensáveis no arsenal do desenvolvedor JavaScript moderno. Ao dominar esses recursos poderosos, você pode desbloquear novas dimensões de expressividade e eficiência no seu código assíncrono. Seja construindo aplicações web, APIs do lado do servidor ou utilitários de linha de comando, os iteradores assíncronos e geradores capacitam você a enfrentar desafios assíncronos complexos com facilidade. Comece a incorporar iteradores assíncronos e geradores em seus projetos JavaScript hoje e eleve suas habilidades de programação a novos patamares!
Tópicos Relacionados
- Iteráveis — a base síncrona por trás do
for...ofe doSymbol.iterator. - Geradores — a sintaxe
function*sobre a qual os geradores assíncronos são construídos. - Promises — o que cada
awaitdentro de um gerador assíncrono resolve. - Async/await — a sintaxe com a qual
for await...ofé utilizado em conjunto.