Guia Completo de Async Generators e Async Iterators em JavaScript na Prática Já leu

Entendendo Async Iterators Um async iterator é um objeto que implementa o protocolo assíncrono de iteração, permitindo percorrer dados que chegam de forma assíncrona. Diferentemente dos iteradores comuns, ele retorna uma Promise que resolve para . Para ser um async iterator, um objeto precisa implementar o método , que retorna o próprio iterator com um método que retorna uma Promise. Isso é fundamental quando trabalhamos com fluxos de dados, APIs ou leitura de arquivos que não estão imediatamente disponíveis. Async Generators na Prática Um async generator é uma função que combina e , permitindo pausar a execução com e retornar valores com . É a forma mais elegante e legível de criar async iterators. Generators assíncrono são especialmente úteis para consumir APIs, processar grandes volumes de dados ou implementar padrões de produção-consumo. A função retorna automaticamente um async iterator quando chamada. ${apiUrl}?page=${page}&limit=${pageSize} Exemplo com Leitura de Arquivo Padrões Avançados e Casos de Uso Composição e Transformação Você pode encadear múltiplos

Entendendo Async Iterators

Um async iterator é um objeto que implementa o protocolo assíncrono de iteração, permitindo percorrer dados que chegam de forma assíncrona. Diferentemente dos iteradores comuns, ele retorna uma Promise que resolve para { value, done }.

Para ser um async iterator, um objeto precisa implementar o método [Symbol.asyncIterator](), que retorna o próprio iterator com um método next() que retorna uma Promise. Isso é fundamental quando trabalhamos com fluxos de dados, APIs ou leitura de arquivos que não estão imediatamente disponíveis.

// Exemplo: Iterator assíncrono simples
const asyncIterator = {
  counter: 0,
  [Symbol.asyncIterator]() {
    return this;
  },
  async next() {
    this.counter++;
    if (this.counter <= 3) {
      return { value: `Item ${this.counter}`, done: false };
    }
    return { done: true };
  }
};

// Usando for await...of
(async () => {
  for await (const item of asyncIterator) {
    console.log(item); // Item 1, Item 2, Item 3
  }
})();

Async Generators na Prática

Um async generator é uma função que combina async e function*, permitindo pausar a execução com await e retornar valores com yield. É a forma mais elegante e legível de criar async iterators.

Generators assíncrono são especialmente úteis para consumir APIs, processar grandes volumes de dados ou implementar padrões de produção-consumo. A função async function* retorna automaticamente um async iterator quando chamada.

// Exemplo: Fetchando dados paginados
async function* fetchPaginatedData(apiUrl, pageSize) {
  let page = 1;
  let hasMore = true;

  while (hasMore) {
    const response = await fetch(`${apiUrl}?page=${page}&limit=${pageSize}`);
    const data = await response.json();

    if (data.items.length === 0) {
      hasMore = false;
    } else {
      for (const item of data.items) {
        yield item;
      }
      page++;
    }
  }
}

// Consumindo
(async () => {
  for await (const item of fetchPaginatedData('https://api.example.com/users', 10)) {
    console.log(item.name);
  }
})();

Exemplo com Leitura de Arquivo

// Lendo arquivo linha por linha (Node.js)
const fs = require('fs');
const readline = require('readline');

async function* readFileLines(filePath) {
  const fileStream = fs.createReadStream(filePath);
  const rl = readline.createInterface({
    input: fileStream,
    crlfDelay: Infinity
  });

  for await (const line of rl) {
    yield line;
  }
}

// Usando
(async () => {
  for await (const line of readFileLines('dados.txt')) {
    console.log(line);
  }
})();

Padrões Avançados e Casos de Uso

Composição e Transformação

Você pode encadear múltiplos async generators para criar pipelines de dados elegantes. Isso é particularmente poderoso para processamento ETL (Extract, Transform, Load).

// Filtrando dados
async function* filter(asyncIterable, predicate) {
  for await (const item of asyncIterable) {
    if (await predicate(item)) {
      yield item;
    }
  }
}

// Transformando dados
async function* map(asyncIterable, transform) {
  for await (const item of asyncIterable) {
    yield await transform(item);
  }
}

// Usando pipeline
(async () => {
  const users = async function* () {
    yield { id: 1, name: 'Alice', age: 28 };
    yield { id: 2, name: 'Bob', age: 17 };
    yield { id: 3, name: 'Charlie', age: 35 };
  };

  const adults = filter(users(), user => user.age >= 18);
  const names = map(adults, user => user.name.toUpperCase());

  for await (const name of names) {
    console.log(name); // ALICE, CHARLIE
  }
})();

Tratamento de Erros

Erros em async generators devem ser tratados com try/catch tanto na função quanto durante a consumição, garantindo robustez.

async function* safeDataFetch(urls) {
  for (const url of urls) {
    try {
      const response = await fetch(url);
      if (!response.ok) throw new Error(`HTTP ${response.status}`);
      yield await response.json();
    } catch (error) {
      console.error(`Erro ao buscar ${url}:`, error.message);
      yield null; // ou use `continue;` para pular
    }
  }
}

(async () => {
  for await (const data of safeDataFetch(['url1', 'url2', 'url3'])) {
    if (data) console.log(data);
  }
})();

Performance e Boas Práticas

Async generators são eficientes em memória porque processam um item por vez, ideal para grandes datasets. Nunca coloque toda a lógica assíncrona dentro de um único await no meio do generator — isso pode travar a iteração.

Dica importante: Use for await...of apenas quando realmente precisar de assincronicidade. Para arrays normais, continue usando for ou métodos como map() e filter().

// ❌ Ineficiente: carregando tudo na memória
async function* badGenerator() {
  const allData = await fetch('/api/huge-dataset').then(r => r.json());
  for (const item of allData) {
    yield item;
  }
}

// ✅ Eficiente: carregando sob demanda
async function* goodGenerator() {
  let page = 1;
  let hasData = true;

  while (hasData) {
    const data = await fetch(`/api/data?page=${page}`).then(r => r.json());
    if (data.length === 0) hasData = false;

    for (const item of data) {
      yield item;
    }
    page++;
  }
}

Conclusão

Os principais aprendizados são: (1) Async iterators e generators oferecem uma forma elegante e eficiente de trabalhar com dados assíncrono em JavaScript, especialmente quando combinados com for await...of; (2) generators assíncrono (async function*) são a abordagem recomendada na maioria dos casos por sua legibilidade e capacidade de pausar/resumir com await e yield; (3) padrões de composição (filter, map, etc.) permitem criar pipelines robustos de processamento de dados sem carregar tudo na memória.

Referências


Artigos relacionados