Executando verificação de segurança...
1

JavaScript avançado: Generators e Iterators, o que são, onde vivem?

A iteração nos permite navegar pelos dados de maneira eficiente, especialmente quando estamos lidando com dados assíncronos que chegam sob demanda, como no caso de downloads em partes. Os generators tornam essa tarefa ainda mais conveniente ao fornecer uma forma elegante de pausar e retomar a execução de funções.

Hoje vamos aprender um pouquinho mais de um recurso extremamente interessante que pode te levar pra um outro nível no JavaScript!

O que são Iterators e Iterable objects?

Iterable objects são um tipo de generalização de arrays, essa generalização nos permite por exemplo utilizar objetos em um loop do tipo for .. of.

Arrays são iteráveis, e isso faz nossa vida muito fácil, mas, apenas os arrays? Não! Na verdade, qualquer objeto que represente uma coleção (listas, sets, vetores) pode ser um ótimo candidato à iteração. Como por exemplo as strings.

for (const a of 'any'){
    console.log(a)
}
// console log: a
// console log: n
// console log: y

Como usar e caso de uso: Paginação

Sabendo disso, como poderíamos implementar a paginação utilizando de iterators?

class Paginator {
  constructor(items, itemsPerPage) {
    this.items = items;
    this.itemsPerPage = itemsPerPage;
    this.currentPage = 0;
  }

  [Symbol.iterator]() {
    return {
      items: this.items,
      itemsPerPage: this.itemsPerPage,
      currentPage: this.currentPage,
      next() {
        if (this.currentPage * this.itemsPerPage >= this.items.length) {
          return { done: true };
        }
        const start = this.currentPage * this.itemsPerPage;
        const end = start + this.itemsPerPage;
        this.currentPage++;
        return { value: this.items.slice(start, end), done: false };
      }
    };
  }
}

Para criarmos um objeto que tire vantagem da implementação de iteráveis, este objeto deverá implementar o [Symbol.iterator] este método irá permitir que nossa classe seja iterada usando uma estrutura de iteração como o for .. of.
Para a implementação deste método, devemos retornar o método next(), ele é necessário pois define como a iteração deve proceder em uma sequência de elementos. Cada chamada ao next deve retornar um objeto com duas propriedades:

  • done: Um booleano que indica se a iteração foi concluída.
  • value: O valor atual da iteração (se done for false).

E os Async Iterators?

Async iterators são uma extensão do conceito de iterators, permitindo a iteração sobre dados assíncronos. Eles são particularmente úteis quando você precisa lidar com operações de entrada/saída (I/O) ou requisições de rede que retornam dados de maneira assíncrona.

Para implementar um Async Iterator:

  1. Use Symbol.asyncIterator em vez de Symbol.iterator.
  2. O método next() deve retornar uma Promise.
    • Basta implementar o método como async next().
  3. Para iterar sobre tal objeto, devemos usar um loop for await (let item of iterable).
    • Alteramos aqui apenas a palavra await.

Quando se implementa um async iterator o operador de spread ... não funciona. O mesmo utiliza apenas de iterators.

O que são Generators?

Os generators são simplesmente uma forma de implementar iterators fora de uma classe, como uma função.

Generators são criados utilizando um asterisco ao se definir uma função function* e utiliza de yield para gerar os valores, e assim podemos utilizar em um loop for .. of tal como os iterators.

function* fibonacciGenerator() {
    let a = 0, b = 1;
    while (true) {
        yield a; // Retorna o número de Fibonacci atual
        [a, b] = [b, a + b]; // Calcula o próximo número da sequencia de Fibonacci
    }
}

E aqui, a utilização pode ser a mesma dos iterators! Tanto utilizando o método next quanto em um loop for .. of (não exatamente esse gerador de fibonacci porque ele não termina nunca hein!)

const fibo = fibonacciGenerator()
console.log(fibo.next()) // {value: 0, done: false}
console.log(fibo.next()) // {value: 1, done: false}
console.log(fibo.next()) // {value: 2, done: false}
console.log(fibo.next()) // {value: 3, done: false}
console.log(fibo.next()) // {value: 5, done: false}

E os Async Generators?

Async Generators são uma combinação poderosa de generators e async iterators. Eles permitem que você defina funções que podem pausar sua execução (yield) e, ao mesmo tempo, realizar operações assíncronas. Isso é extremamente útil para cenários onde você precisa processar dados assíncronos em partes, como ao consumir APIs paginadas ou fluxos de dados contínuos.

Para implementar um async generator:

  1. Use async function* para definir um async generator.
  2. Utilize await dentro do corpo da função e yield para pausar a execução.
  3. Utilize for await...of para iterar sobre os valores gerados.

Exemplo na vida real: Todos registros de uma API paginada

Imagine que você está consumindo uma API paginada que retorna um conjunto de resultados em cada página, mas por algum motivo, em seu caso de uso, é necessário que você retorne todos os valores possíveis.

async function* fetchPagedData() {
  let currentPage = 1;
  let hasMoreData = true;

  while (hasMoreData) {
    const response = await fetch(`https://example.com/?page=${currentPage}`);
    const data = await response.json();

    if (data.items.length === 0) {
      hasMoreData = false;
    } else {
      yield data.items;
      currentPage++;
    }
  }
}

Para utilizar seu generator assíncrono:

const pageContents = [];

for await (const items of fetchPagedData(apiEndpoint, pageSize)) {
  pageContents.push(...items);
}

Dessa forma você teria todos os itens de forma extremamente elegante!


Embora as aplicações de async generators sejam raras, elas podem ser usadas em momentos como streaming de dados, feeds de tempo real e em processamento de grandes dados.

Então, mesmo que você não precise utilizar hoje, ter ciência dessas técnicas é imprescindível para que você saiba o que fazer quando precisar, elevando assim suas habilidades com o Javascript a um novo nível!

Carregando publicação patrocinada...