Como Usar Iteração Avançada em JavaScript: Iterables, Generators Assíncronos em Produção Já leu

Iterables e o Protocolo de Iteração Um iterable é um objeto que implementa o protocolo iterável do JavaScript, permitindo ser percorrido em loops como . Para ser iterable, um objeto deve ter um método que retorna um iterador — um objeto com um método que produz valores sequencialmente. Async generators são ideais para processar streams de dados, como ler linhas de um arquivo, consumir eventos de um servidor, ou paginar resultados de uma API. O aguarda automaticamente cada Promise, mantendo o código sincronizado e legível. Casos Práticos: Paginação e Streams Um exemplo real é iterar sobre resultados paginados sem conhecer o total de páginas antecipadamente. O async generator busca a próxima página conforme necessário: ${url}?page=${pagina} Esse padrão elimina a necessidade de fazer todas as requisições antecipadamente, economizando memória e permitindo cancelar a iteração a qualquer momento. Conclusão Iterables, generators e async generators formam uma progressão natural em JavaScript avançado. Começando com o protocolo iterável, você ganha controle fino sobre iterações

Iterables e o Protocolo de Iteração

Um iterable é um objeto que implementa o protocolo iterável do JavaScript, permitindo ser percorrido em loops como for...of. Para ser iterable, um objeto deve ter um método Symbol.iterator que retorna um iterador — um objeto com um método next() que produz valores sequencialmente.

// Criando um iterable customizado
const intervalo = {
  inicio: 1,
  fim: 5,
  [Symbol.iterator]() {
    let atual = this.inicio;
    return {
      next: () => {
        if (atual <= this.fim) {
          return { value: atual++, done: false };
        }
        return { done: true };
      }
    };
  }
};

for (const num of intervalo) {
  console.log(num); // 1, 2, 3, 4, 5
}

Entender esse protocolo é fundamental porque ele é a base de todas as iterações avançadas em JavaScript. Arrays, Strings, Maps e Sets já implementam isso nativamente. Quando você cria um iterable customizado, ganha controle total sobre como os dados são consumidos, ideal para estruturas de dados complexas ou infinitas.

Diferença entre Iterable e Iterator

Um iterable é quem pode ser iterado, enquanto um iterator é quem controla a iteração. O iterator mantém estado interno (qual é o próximo valor) e possui o método next(). Um mesmo iterable pode ter múltiplos iteradores independentes operando simultaneamente.

const numeros = [1, 2, 3];
const iter1 = numeros[Symbol.iterator]();
const iter2 = numeros[Symbol.iterator]();

console.log(iter1.next()); // { value: 1, done: false }
console.log(iter2.next()); // { value: 1, done: false }
console.log(iter1.next()); // { value: 2, done: false }

Generators: Simplificando a Iteração

Generators são funções especiais que retornam iterators de forma muito mais simples. Usando a sintaxe function* e a palavra-chave yield, você escreve código iterativo de forma linear, sem precisar gerenciar manualmente o método next() e o estado.

function* contador(max) {
  for (let i = 1; i <= max; i++) {
    yield i;
  }
}

const gen = contador(3);
console.log(gen.next()); // { value: 1, done: false }
console.log(gen.next()); // { value: 2, done: false }
console.log(gen.next()); // { value: 3, done: false }
console.log(gen.next()); // { value: undefined, done: true }

// Também funciona em for...of
for (const num of contador(3)) {
  console.log(num); // 1, 2, 3
}

O grande benefício é a legibilidade: você não precisa criar objetos com estruturas complexas. Generators mantêm o estado automaticamente, pausando na execução em cada yield e retomando de onde pararam. Isso é especialmente poderoso para processar grandes volumes de dados sem carregar tudo na memória.

Generators com Comunicação Bidirecional

Um aspecto avançado dos generators é a capacidade de receber valores através do método next(). Ao chamar next(valor), esse valor é retornado da expressão yield anterior, permitindo comunicação bidirecional.

function* dialogoSimples() {
  const resposta1 = yield "Qual seu nome?";
  const resposta2 = yield `Olá ${resposta1}, quantos anos você tem?`;
  console.log(`${resposta1} tem ${resposta2} anos`);
}

const gen = dialogoSimples();
console.log(gen.next().value);           // "Qual seu nome?"
console.log(gen.next("Maria").value);    // "Olá Maria, quantos anos você tem?"
gen.next(25);                            // "Maria tem 25 anos"

Generators Assíncronos: Iteração com Async/Await

Um async generator combina generators com async/await, permitindo iterar sobre dados assíncronos de forma elegante. A sintaxe é async function* e pode usar yield com Promises, await ou ambos.

// Async generator que simula buscar dados de uma API
async function* buscarDados(ids) {
  for (const id of ids) {
    const resposta = await fetch(`https://api.exemplo.com/usuario/${id}`);
    const dados = await resposta.json();
    yield dados;
  }
}

// Consumindo com for await...of
(async () => {
  for await (const usuario of buscarDados([1, 2, 3])) {
    console.log(usuario.nome);
  }
})();

Async generators são ideais para processar streams de dados, como ler linhas de um arquivo, consumir eventos de um servidor, ou paginar resultados de uma API. O for await...of aguarda automaticamente cada Promise, mantendo o código sincronizado e legível.

Casos Práticos: Paginação e Streams

Um exemplo real é iterar sobre resultados paginados sem conhecer o total de páginas antecipadamente. O async generator busca a próxima página conforme necessário:

async function* paginarResultados(url) {
  let pagina = 1;

  while (true) {
    const resposta = await fetch(`${url}?page=${pagina}`);
    const dados = await resposta.json();

    if (!dados.items || dados.items.length === 0) break;

    for (const item of dados.items) {
      yield item;
    }

    pagina++;
  }
}

// Uso simples
(async () => {
  for await (const item of paginarResultados('https://api.exemplo.com')) {
    console.log(item);
  }
})();

Esse padrão elimina a necessidade de fazer todas as requisições antecipadamente, economizando memória e permitindo cancelar a iteração a qualquer momento.

Conclusão

Iterables, generators e async generators formam uma progressão natural em JavaScript avançado. Começando com o protocolo iterável, você ganha controle fino sobre iterações customizadas. Generators simplificam drasticamente essa lógica sem sacrificar funcionalidade. Async generators, por sua vez, trazem elegância para operações assíncronas complexas, substituindo callbacks e promises aninhadas por código linear e legível. Dominar esses três conceitos permite escrever código mais eficiente, escalável e profissional.

Referências


Artigos relacionados