Generators e Iteradores em JavaScript na Prática Já leu

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 que retorna um objeto com duas propriedades: (o valor atual) e (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 : Note o e — 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: ${prefixo}-${contador++} Conclusão Iteradores e generators são ferramentas poderosas que você usará constantemente em JavaScript moderno. Primeiro aprendizado: entender que todo objeto com é iterável, permitindo usar 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 ( e ) são essenciais para trabalhar

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.

Referências


Artigos relacionados