Back

Começando com os Helpers de Iterator do JavaScript

Começando com os Helpers de Iterator do JavaScript

Se você já tentou processar um conjunto de dados massivo em JavaScript, conhece a dor. Métodos tradicionais de array como .map() e .filter() forçam você a carregar tudo na memória de uma só vez. Tente isso com um milhão de registros ou um fluxo de dados infinito, e sua aplicação trava. Os helpers de iterator do JavaScript resolvem esse problema trazendo avaliação lazy para o núcleo da linguagem.

Este artigo mostra como usar os novos métodos helper de iterator, entender seus benefícios de performance e aplicá-los a cenários do mundo real como processamento de arquivos grandes, manipulação de streams de API e trabalho com sequências infinitas.

Principais Pontos

  • Helpers de iterator fornecem avaliação lazy para processamento de dados eficiente em memória
  • Converta arrays com .values() e outros iteráveis com Iterator.from()
  • Métodos como .map(), .filter() e .take() se encadeiam sem criar arrays intermediários
  • Perfeito para sequências infinitas, arquivos grandes e dados de streaming
  • Uso único apenas - crie novos iterators para múltiplas iterações

Entendendo o Protocolo Iterator

Antes de mergulhar nos novos helpers, vamos esclarecer o que torna os iterators especiais. Um iterator é simplesmente um objeto com um método .next() que retorna pares {value, done}:

const iterator = {
  current: 0,
  next() {
    return this.current < 3 
      ? { value: this.current++, done: false }
      : { done: true }
  }
}

console.log(iterator.next()) // { value: 0, done: false }
console.log(iterator.next()) // { value: 1, done: false }

Arrays, Sets, Maps e generators implementam o protocolo iterator através de seu método [Symbol.iterator](). Este protocolo alimenta loops for...of e o operador spread, mas até recentemente, iterators careciam dos métodos de programação funcional que os desenvolvedores esperam.

Helpers de Iterator do JavaScript: O Que Há de Novo

Os helpers de iterator estendem o protótipo Iterator com métodos que espelham operações de array mas funcionam de forma lazy:

MétodoDescriçãoRetorna
.map(fn)Transforma cada valorIterator
.filter(fn)Produz valores que passam no testeIterator
.take(n)Produz os primeiros n valoresIterator
.drop(n)Pula os primeiros n valoresIterator
.flatMap(fn)Mapeia e achata resultadosIterator
.reduce(fn, init)Agrega para um único valorValor
.find(fn)Primeiro valor que passa no testeValor
.some(fn)Testa se algum valor passaBoolean
.every(fn)Testa se todos os valores passamBoolean
.toArray()Coleta todos os valoresArray

Para usar esses métodos, converta sua estrutura de dados para um iterator primeiro:

// Para arrays
const result = [1, 2, 3, 4, 5]
  .values()  // Converte para iterator
  .filter(x => x % 2 === 0)
  .map(x => x * 2)
  .toArray()  // [4, 8]

// Para outros iteráveis
const set = new Set([1, 2, 3])
const doubled = Iterator.from(set)
  .map(x => x * 2)
  .toArray()  // [2, 4, 6]

Avaliação Lazy vs Eager: A Diferença Principal

Métodos tradicionais de array processam tudo imediatamente:

// Eager - processa todos os elementos imediatamente
const eager = [1, 2, 3, 4, 5]
  .map(x => {
    console.log(`Mapeando ${x}`)
    return x * 2
  })
  .filter(x => x > 5)

// Registra: Mapeando 1, 2, 3, 4, 5
// Resultado: [6, 8, 10]

Helpers de iterator processam valores apenas quando consumidos:

// Lazy - processa apenas o que é necessário
const lazy = [1, 2, 3, 4, 5]
  .values()
  .map(x => {
    console.log(`Mapeando ${x}`)
    return x * 2
  })
  .filter(x => x > 5)
  .take(2)

// Nada registrado ainda!

const result = [...lazy]
// Registra: Mapeando 1, 2, 3
// Resultado: [6, 8]

Note como a versão lazy para após encontrar dois valores correspondentes, nunca processando os elementos 4 e 5. Essa eficiência torna-se crucial ao trabalhar com grandes conjuntos de dados.

Exemplos Práticos e Casos de Uso

Processando Arquivos Grandes Linha por Linha

Em vez de carregar um arquivo inteiro na memória:

async function* readLines(file) {
  const reader = file.stream().getReader()
  const decoder = new TextDecoder()
  let buffer = ''
  
  while (true) {
    const { done, value } = await reader.read()
    if (done) break
    
    buffer += decoder.decode(value, { stream: true })
    const lines = buffer.split('\n')
    buffer = lines.pop()
    
    for (const line of lines) yield line
  }
  if (buffer) yield buffer
}

// Processa CSV sem carregar arquivo inteiro
const validRecords = await readLines(csvFile)
  .drop(1)  // Pula cabeçalho
  .map(line => line.split(','))
  .filter(cols => cols[2] === 'active')
  .take(100)
  .toArray()

Trabalhando com Sequências Infinitas

Gere e processe fluxos de dados infinitos:

function* fibonacci() {
  let [a, b] = [0, 1]
  while (true) {
    yield a
    ;[a, b] = [b, a + b]
  }
}

// Encontra primeiro número de Fibonacci maior que 1000
const firstLarge = fibonacci()
  .find(n => n > 1000)  // 1597

// Obtém os primeiros 10 números de Fibonacci pares
const evenFibs = fibonacci()
  .filter(n => n % 2 === 0)
  .take(10)
  .toArray()

Paginação de API Sem Sobrecarga de Memória

Manipule APIs paginadas eficientemente:

async function* fetchAllUsers(apiUrl) {
  let page = 1
  while (true) {
    const response = await fetch(`${apiUrl}?page=${page}`)
    const { data, hasMore } = await response.json()
    
    for (const user of data) yield user
    
    if (!hasMore) break
    page++
  }
}

// Processa usuários sem carregar todas as páginas
const premiumUsers = await fetchAllUsers('/api/users')
  .filter(user => user.subscription === 'premium')
  .map(user => ({ id: user.id, email: user.email }))
  .take(50)
  .toArray()

Considerações de Performance e Uso de Memória

Helpers de iterator brilham quando:

  • Processando dados maiores que a memória disponível
  • Você precisa apenas de um subconjunto de resultados
  • Encadeando múltiplas transformações
  • Trabalhando com streams ou dados em tempo real

Eles são menos adequados quando:

  • Você precisa de acesso aleatório aos elementos
  • O conjunto de dados é pequeno e já está na memória
  • Você precisa iterar múltiplas vezes (iterators são de uso único)

Aqui está uma comparação de memória:

// Abordagem de array intensiva em memória
function processLargeDataArray(data) {
  return data
    .map(transform)      // Cria novo array
    .filter(condition)   // Cria outro array
    .slice(0, 100)       // Cria terceiro array
}

// Abordagem de iterator eficiente em memória
function processLargeDataIterator(data) {
  return data
    .values()
    .map(transform)      // Sem array intermediário
    .filter(condition)   // Sem array intermediário
    .take(100)
    .toArray()           // Apenas os 100 itens finais na memória
}

Suporte de Navegador e Polyfills

Os helpers de iterator do JavaScript são suportados em:

  • Chrome 122+
  • Firefox 131+
  • Safari 18.4+
  • Node.js 22+

Para ambientes mais antigos, use o polyfill es-iterator-helpers:

npm install es-iterator-helpers

Armadilhas Comuns e Soluções

Iterators São de Uso Único

const iter = [1, 2, 3].values().map(x => x * 2)
console.log([...iter])  // [2, 4, 6]
console.log([...iter])  // [] - Já consumido!

// Solução: Crie um novo iterator
const makeIter = () => [1, 2, 3].values().map(x => x * 2)

Misturando Métodos de Iterator e Array

// Não funcionará - filter retorna iterator, não array
const result = [1, 2, 3]
  .values()
  .filter(x => x > 1)
  .includes(2)  // Erro!

// Solução: Converta de volta para array primeiro
const result = [1, 2, 3]
  .values()
  .filter(x => x > 1)
  .toArray()
  .includes(2)  // true

Conclusão

Os helpers de iterator do JavaScript trazem programação funcional para avaliação lazy, tornando possível o processamento eficiente de conjuntos de dados grandes ou infinitos. Ao entender quando usar .values() ou Iterator.from() e como a avaliação lazy difere dos métodos eager de array, você pode escrever código eficiente em memória que escala. Comece a usar esses métodos para dados de streaming, paginação e qualquer cenário onde carregar tudo na memória não é prático.

FAQs

Os helpers de iterator padrão funcionam apenas com iterators síncronos. Para operações assíncronas, você precisará aguardar pelos helpers de iterator assíncronos (propostos para futuras versões do ES) ou usar bibliotecas que fornecem suporte à iteração assíncrona.

Os helpers de iterator fornecem avaliação lazy básica incorporada na linguagem, enquanto o RxJS oferece recursos avançados como tratamento de erro, backpressure e operadores complexos. Use helpers de iterator para transformações simples e RxJS para programação reativa complexa.

Não, os métodos de array permanecem a melhor escolha para pequenos conjuntos de dados que cabem na memória e quando você precisa de acesso aleatório ou múltiplas iterações. Os helpers de iterator complementam arrays para casos de uso específicos envolvendo dados grandes ou infinitos.

Sim, estenda a classe Iterator ou use Iterator.from() com um objeto personalizado implementando o protocolo iterator. Isso permite adicionar transformações específicas do domínio mantendo compatibilidade com helpers incorporados.

Listen to your bugs 🧘, with OpenReplay

See how users use your app and resolve issues fast.
Loved by thousands of developers