🚀 Oferta especial: 60% OFF no CrazyStack - Últimas vagas!Garantir vaga →
MÓDULO 2 - AULA 4

Iterator Helpers: Map, Filter e Take

A evolução dos arrays para generators. Pipeline de transformação sob demanda com lazy evaluation

🎯 A Revolução dos Iterator Helpers

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.

🏭 A linha de produção moderna

⚠️ Do futuro para o presente

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.

iterator-helpers.js
// 📚 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: O poder da preguiça

✅ Por que "preguiça" é boa

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.

lazy-evaluation.js
// 🔄 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

🔧 Pipeline Prático: Processamento de Usuários

🎯 Caso Real de Uso

Vamos simular um processamento real: filtrar usuários ativos, transformar dados e limitar resultados - tudo sob demanda para economizar recursos.

practical-pipeline.js
// 🧩 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: Iterator Helpers em Streams

🚀 No Node.js atual

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.

node-stream-helpers.js
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();

🚀 Checkpoint: Pipeline Master

Iterator HelpersLazy EvaluationPipeline PatternNode.js Streams

Você agora domina pipelines eficientes que processam apenas o necessário!