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 comIterator.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étodo | Descrição | Retorna |
---|---|---|
.map(fn) | Transforma cada valor | Iterator |
.filter(fn) | Produz valores que passam no teste | Iterator |
.take(n) | Produz os primeiros n valores | Iterator |
.drop(n) | Pula os primeiros n valores | Iterator |
.flatMap(fn) | Mapeia e achata resultados | Iterator |
.reduce(fn, init) | Agrega para um único valor | Valor |
.find(fn) | Primeiro valor que passa no teste | Valor |
.some(fn) | Testa se algum valor passa | Boolean |
.every(fn) | Testa se todos os valores passam | Boolean |
.toArray() | Coleta todos os valores | Array |
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.