A evolução dos arrays para generators. Pipeline de transformação sob demanda com lazy evaluation
A primeira proposta que está disponível na maioria dos lugares que rodam JavaScript são os Iterator Helpers. Se fosse para resumir: é pensar que funções como map
, filter
e reduce
, que temos em arrays, agora podem se aplicar para funções de processamento sob demanda.
Node.js já implementou as funções de filter
, map
e outras que vão estar disponíveis no JavaScript no futuro. Você pode trabalhar com map
, filter
, take
e vários outros métodos, como se estivesse trabalhando com dados em memória, mas agora processando dados sob demanda.
// 📚 CONCEITO: Iterator Helpers = .map(), .filter() para generators
// Pense numa linha de produção com estações de trabalho
function* infiniteNumbers() {
let i = 0;
while (true) {
yield i++; // 🏭 Fábrica de números infinitos
}
}
// 🔮 FUTURO: Iterator Helpers nativos (proposal em andamento)
// Será possível fazer isso quando estiver pronto:
// const result = infiniteNumbers()
// .map(x => x * 2) // 🔧 Estação 1: Multiplica por 2
// .filter(x => x % 3 === 0) // 🔍 Estação 2: Filtra múltiplos de 3
// .take(5); // ✂️ Estação 3: Pega apenas 5
// 🛠️ POR ENQUANTO: Implementação manual dos helpers
function map(iterator, fn) {
return (function* () { // 🔄 Retorna novo generator
for (const value of iterator) {
yield fn(value); // 🔧 Aplica transformação
}
})();
}
function filter(iterator, predicate) {
return (function* () { // 🔄 Retorna novo generator
for (const value of iterator) {
if (predicate(value)) { // 🔍 Testa condição
yield value; // ✅ Passa apenas se válido
}
}
})();
}
function take(iterator, count) {
return (function* () { // 🔄 Retorna novo generator
let taken = 0;
for (const value of iterator) {
if (taken >= count) break; // ✂️ Para no limite
yield value;
taken++;
}
})();
}
// 🏭 LINHA DE PRODUÇÃO: Combina as estações
const pipeline = take( // 3️⃣ Pega só 5 resultados
filter( // 2️⃣ Filtra múltiplos de 3
map(infiniteNumbers(), x => x * 2), // 1️⃣ Multiplica por 2
x => x % 3 === 0
),
5
);
console.log([...pipeline]); // [0, 6, 12, 18, 24]
// 💡 VANTAGEM: Processamento lazy - só calcula quando precisa!
// Mesmo sendo "infinito", só processa 5 números
Lazy evaluation significa que cada etapa do pipeline só é executada quando o valor é realmente necessário. Isso permite trabalhar com sequências infinitas e otimizar performance automaticamente.
// 🔄 DEMONSTRAÇÃO: Lazy vs Eager evaluation
console.log('=== COMPARAÇÃO: EAGER vs LAZY ===');
// ❌ EAGER (Arrays tradicionais): Processa TUDO de uma vez
function eagerProcessing() {
console.log('📊 EAGER: Processando arrays tradicionais...');
const numbers = Array.from({ length: 1000000 }, (_, i) => i); // 1 milhão
console.log(' ✅ Array criado: 1 milhão de números');
const doubled = numbers.map(x => x * 2);
console.log(' ✅ Todos multiplicados por 2');
const filtered = doubled.filter(x => x % 3 === 0);
console.log(' ✅ Todos filtrados (múltiplos de 3)');
const first5 = filtered.slice(0, 5);
console.log(' ✅ Pegou apenas 5:', first5);
// 🔥 PROBLEMA: Processou 1 milhão para usar apenas 5!
}
// ✅ LAZY (Iterator Helpers): Processa SOB DEMANDA
function lazyProcessing() {
console.log('\n⚡ LAZY: Processando sob demanda...');
function* millionNumbers() {
console.log(' 🏭 Iniciando gerador...');
for (let i = 0; i < 1000000; i++) {
console.log(` 🔄 Gerando número: ${i}`);
yield i;
}
}
// 🏭 PIPELINE: Cada etapa só executa quando necessário
const pipeline = take(
filter(
map(millionNumbers(), x => {
console.log(` 🔧 Multiplicando: ${x} * 2`);
return x * 2;
}),
x => {
console.log(` 🔍 Testando: ${x} % 3 === 0`);
return x % 3 === 0;
}
),
5
);
console.log(' ⚡ Pipeline criado (ainda não executou nada!)');
const result = [...pipeline];
console.log(' ✅ Resultado:', result);
// 💡 VANTAGEM: Só processou o necessário para obter 5 resultados!
}
// 🧪 TESTE: Execute ambos e veja a diferença
console.time('Eager');
eagerProcessing();
console.timeEnd('Eager');
console.time('Lazy');
lazyProcessing();
console.timeEnd('Lazy');
// 📊 RESULTADO ESPERADO:
// Eager: ~200ms + muita memória
// Lazy: ~5ms + memória mínima
Vamos simular um processamento real: filtrar usuários ativos, transformar dados e limitar resultados - tudo sob demanda para economizar recursos.
// 🧩 EXEMPLO PRÁTICO: Sistema de usuários
function* generateUsers(count = 10000) {
const names = ['Ana', 'Bruno', 'Carlos', 'Diana', 'Eduardo', 'Fernanda'];
const domains = ['gmail.com', 'yahoo.com', 'hotmail.com'];
for (let i = 0; i < count; i++) {
const name = names[i % names.length];
const domain = domains[i % domains.length];
// 🏭 Simula busca de usuário no banco
console.log(`🔍 Buscando usuário ${i + 1}...`);
yield {
id: i + 1,
name: name + (i + 1),
email: name.toLowerCase() + (i + 1) + '@' + domain,
age: 18 + (i % 60),
active: Math.random() > 0.3, // 70% ativos
premium: Math.random() > 0.7, // 30% premium
lastLogin: new Date(Date.now() - Math.random() * 30 * 24 * 60 * 60 * 1000)
};
}
}
// 🔧 HELPERS CUSTOMIZADOS para processamento
function mapUsers(iterator, transformer) {
return (function* () {
for (const user of iterator) {
console.log(`🔧 Transformando: ${user.name}`);
yield transformer(user);
}
})();
}
function filterUsers(iterator, condition) {
return (function* () {
for (const user of iterator) {
console.log(`🔍 Avaliando: ${user.name}`);
if (condition(user)) {
console.log(` ✅ Aprovado: ${user.name}`);
yield user;
} else {
console.log(` ❌ Rejeitado: ${user.name}`);
}
}
})();
}
function takeUsers(iterator, count) {
return (function* () {
let taken = 0;
for (const user of iterator) {
if (taken >= count) {
console.log(`✂️ Limite atingido: ${count} usuários`);
break;
}
yield user;
taken++;
}
})();
}
// 🚀 PIPELINE COMPLETO: Usuários premium ativos recentes
console.log('=== INICIANDO PIPELINE DE USUÁRIOS ===');
const processedUsers = takeUsers(
filterUsers(
mapUsers(
generateUsers(10000), // 🏭 Fonte: 10mil usuários
user => ({
...user,
// 🔧 ENRIQUECIMENTO: Adiciona dados calculados
fullEmail: user.email.toUpperCase(),
ageGroup: user.age < 30 ? 'jovem' : user.age < 50 ? 'adulto' : 'senior',
daysSinceLogin: Math.floor((Date.now() - user.lastLogin) / (24 * 60 * 60 * 1000)),
processedAt: new Date().toISOString()
})
),
user => user.active && user.premium && user.daysSinceLogin <= 7 // 🔍 FILTRO complexo
),
10 // ✂️ LIMITE: Apenas 10 resultados
);
console.log('\n=== CONSUMINDO RESULTADOS ===');
const results = [...processedUsers];
console.log('\n=== RELATÓRIO FINAL ===');
console.log(`📊 Usuários processados: ${results.length}`);
results.forEach((user, index) => {
console.log(`${index + 1}. ${user.name} (${user.ageGroup}) - ${user.daysSinceLogin} dias`);
});
// 💡 VANTAGEM: Só processou o necessário para obter 10 usuários válidos!
// Se fossem arrays, processaria os 10mil primeiro, desperdiçando recursos.
Node.js se adiantou e já implementou os helpers em cima de streams. Você pode usar Readable.from()
para converter generators e ter acesso aos métodos nativos.
import { Readable } from 'stream';
// 🔄 GERADOR SIMPLES para demonstração
function* numberStream() {
for (let i = 1; i <= 100; i++) {
yield i;
}
}
// 🌐 CONVERTENDO para Stream do Node.js
const readableStream = Readable.from(numberStream());
// 🎯 USANDO HELPER NATIVOS do Node.js (disponível hoje!)
async function demonstrateNodeHelpers() {
console.log('=== NODE.JS ITERATOR HELPERS ===');
try {
// 🏭 PIPELINE com métodos nativos
const result = readableStream
.map(x => x * 2) // 🔧 Multiplica por 2
.filter(x => x % 3 === 0) // 🔍 Filtra múltiplos de 3
.take(5); // ✂️ Pega apenas 5
console.log('✅ Pipeline criado com métodos nativos!');
// 📤 CONSUMINDO o resultado
for await (const value of result) {
console.log('Resultado:', value);
}
} catch (error) {
console.error('❌ Erro:', error.message);
}
}
// 🧪 ALTERNATIVA: Implementação manual para compatibilidade
async function manualNodePipeline() {
console.log('\n=== IMPLEMENTAÇÃO MANUAL ===');
const stream = Readable.from(
(function* () {
for (let i = 1; i <= 100; i++) {
yield i;
}
})()
);
// 🔧 PIPELINE manual
const mapped = Readable.from(mapAsync(stream, x => x * 2));
const filtered = Readable.from(filterAsync(mapped, x => x % 3 === 0));
const limited = Readable.from(takeAsync(filtered, 5));
for await (const value of limited) {
console.log('Manual result:', value);
}
}
// 🛠️ HELPERS ASSÍNCRONOS para compatibilidade
async function* mapAsync(stream, fn) {
for await (const value of stream) {
yield fn(value);
}
}
async function* filterAsync(stream, predicate) {
for await (const value of stream) {
if (predicate(value)) {
yield value;
}
}
}
async function* takeAsync(stream, count) {
let taken = 0;
for await (const value of stream) {
if (taken >= count) break;
yield value;
taken++;
}
}
// 🚀 EXECUTANDO demonstrações
demonstrateNodeHelpers();
manualNodePipeline();
Você agora domina pipelines eficientes que processam apenas o necessário!