Iteradores: A Base de Tudo
Um iterador é um objeto JavaScript que implementa o protocolo iterável, permitindo percorrer uma sequência de valores. Todo iterador possui um método next() que retorna um objeto com duas propriedades: value (o valor atual) e done (booleano indicando se chegou ao fim).
Para entender na prática, considere este exemplo: você precisa criar um iterador que gera números de 1 a 5. Implementamos através de um objeto com o método Symbol.iterator:
const minhaSequencia = {
valor: 1,
[Symbol.iterator]() {
return {
valor: this.valor,
next: () => {
if (this.valor <= 5) {
return { value: this.valor++, done: false };
}
return { done: true };
}
};
}
};
for (const num of minhaSequencia) {
console.log(num); // 1, 2, 3, 4, 5
}
Essa implementação é funcional, mas verbosa. Generators resolvem exatamente esse problema oferecendo uma sintaxe muito mais limpa.
Generators: Iteradores Simplificados
Um generator é uma função especial que pode pausar sua execução e retomar depois. Você a declara com function* e usa yield para pausar. Quando chamada, retorna um iterador automaticamente.
Aqui está o mesmo exemplo anterior, mas muito mais elegante:
function* minhaSequencia() {
for (let i = 1; i <= 5; i++) {
yield i;
}
}
for (const num of minhaSequencia()) {
console.log(num); // 1, 2, 3, 4, 5
}
A diferença é notável: o generator faz o mesmo trabalho com menos linhas e maior legibilidade. Quando você chama minhaSequencia(), não executa a função imediatamente — retorna um iterador. Cada vez que next() é chamado (implicitamente no for...of), a execução continua até o próximo yield.
Dois Casos de Uso Cruciais
Gerando sequências infinitas: Você pode criar geradores que produzem valores infinitos sem criar arrays em memória:
function* infinito() {
let contador = 0;
while (true) {
yield contador++;
}
}
const gen = infinito();
console.log(gen.next().value); // 0
console.log(gen.next().value); // 1
console.log(gen.next().value); // 2
Delegando a outro generator: Use yield* para reutilizar generators dentro de generators:
function* geradorA() {
yield 1;
yield 2;
}
function* geradorB() {
yield* geradorA();
yield 3;
}
for (const valor of geradorB()) {
console.log(valor); // 1, 2, 3
}
Aplicações Práticas no Mundo Real
Processando Grandes Arquivos
Imagine processar um arquivo CSV gigantesco linha por linha sem carregar tudo na memória:
function* lerArquivoLinhasPorLinhas(conteudo) {
const linhas = conteudo.split('\n');
for (const linha of linhas) {
yield linha.trim();
}
}
const dados = "nome,idade\nJoão,25\nMaria,30\nPedro,28";
for (const linha of lerArquivoLinhasPorLinhas(dados)) {
if (linha && !linha.startsWith('nome')) {
const [nome, idade] = linha.split(',');
console.log(`${nome} tem ${idade} anos`);
}
}
API com Paginação
Generators simplificam muito o consumo de APIs paginadas. Você não precisa gerenciar estado manualmente:
async function* buscarPaginas(url) {
let pagina = 1;
let temProxima = true;
while (temProxima) {
const resposta = await fetch(`${url}?page=${pagina}`);
const dados = await resposta.json();
yield dados.itens;
temProxima = dados.temProxima;
pagina++;
}
}
// Uso:
(async () => {
for await (const itens of buscarPaginas('https://api.exemplo.com/dados')) {
console.log('Processando', itens.length, 'itens');
}
})();
Note o async function* e for await...of — essa é a forma moderna de trabalhar com generators assíncronos.
Gerando IDs Únicos
Um padrão muito prático é usar generators para gerar sequências de IDs:
function* gerarIds(prefixo = 'ID') {
let contador = 1;
while (true) {
yield `${prefixo}-${contador++}`;
}
}
const ids = gerarIds('USER');
console.log(ids.next().value); // USER-1
console.log(ids.next().value); // USER-2
console.log(ids.next().value); // USER-3
Conclusão
Iteradores e generators são ferramentas poderosas que você usará constantemente em JavaScript moderno. Primeiro aprendizado: entender que todo objeto com Symbol.iterator é iterável, permitindo usar for...of e spread operator. Segundo aprendizado: generators são a forma elegante e prática de criar iteradores, economizando linhas de código e melhorando legibilidade. Terceiro aprendizado: generators assíncronos (async function* e for await...of) são essenciais para trabalhar com fluxos de dados assíncronos como APIs paginadas ou streams.
Dominar esses conceitos não é opcional — é fundamental para escrever código JavaScript eficiente e profissional.