Controle manual vs automático. Dominando a arte do processamento controlado
Pela especificação, quem estiver consumindo essa função vai ficar chamando o método .next()
dela, para saber se mais algum valor foi retornado ou deve parar a execução. Ou simplesmente você pode usar o iterator com o for...of
do JavaScript.
Ao chamar uma função geradora, ela retorna um iterador com o método .next()
, que serve para consumir os valores um por um. Cada chamada retorna um objeto com duas propriedades: value e done. Essa abordagem dá controle total sobre quando avançar para o próximo valor.
// 📚 CONCEITO: Controle manual com .next()
// Pense numa linha de montagem onde VOCÊ decide quando avançar
function* dataProcessor() {
yield 'Processando arquivo 1...'; // 🔄 Primeira etapa
yield 'Processando arquivo 2...'; // 🔄 Segunda etapa
yield 'Processando arquivo 3...'; // 🔄 Terceira etapa
return 'Todos os arquivos processados!'; // ✅ Resultado final
}
// 🎮 CONTROLE TOTAL: Você decide quando "apertar play"
const processor = dataProcessor();
// 🔄 EXECUÇÃO PASSO-A-PASSO
let result = processor.next(); // Pega o primeiro status
while (!result.done) { // Enquanto não terminou...
console.log('Status:', result.value); // Mostra progresso atual
// ⏰ PAUSA CONTROLADA: Simula processamento assíncrono
await new Promise(resolve => setTimeout(resolve, 1000));
result = processor.next(); // 👆 SÓ avança quando você quiser!
}
console.log('Resultado final:', result.value);
// 💡 VANTAGEM: Ideal para operações que precisam de pausa entre etapas
// (rate limiting, processamento batch, progresso visual)
Além de usar .next()
manualmente, o JavaScript permite consumir iteradores usando a sintaxe for...of
, que torna o código mais limpo e legível. Essa forma de iteração cuida automaticamente do done
.
// 📚 CONCEITO: for...of - A forma "automática" de consumir
// Pense numa esteira rolante que você só observa passando
function* fibonacci(limit) {
let a = 0, b = 1; // 🌱 Sementes da sequência
while (a <= limit) {
yield a; // 📤 Entrega o número atual
[a, b] = [b, a + b]; // 🔄 Calcula próximos (destructuring)
}
}
// 🎯 FORMA AUTOMÁTICA: for...of cuida do .next() pra você
console.log('Sequência de Fibonacci até 100:');
for (const num of fibonacci(100)) {
console.log(num); // 0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89
// 💡 JavaScript automaticamente chama .next() até done: true
}
// 🔧 CONVERSÃO RÁPIDA: Spread operator pega todos de uma vez
const fibArray = [...fibonacci(20)];
console.log('Array:', fibArray); // [0, 1, 1, 2, 3, 5, 8, 13]
// ⚡ VANTAGEM: Código mais limpo, sem gerenciar .next() manualmente
// ⚠️ CUIDADO: for...of não dá controle sobre pausas entre iterações
Uma das características mais incríveis dos generators é poder criar sequências infinitas sem travar o sistema. Você controla quando parar!
// 🔄 GERADOR INFINITO: Números crescentes sem fim
function* infiniteNumbers() {
let i = 0;
while (true) { // 🔄 Loop infinito (mas controlado!)
yield i++; // 📤 Entrega número atual e incrementa
}
}
// 🎯 USO CONTROLADO: Você decide quando parar
console.log('=== TESTE: Apenas 5 números ===');
let count = 0;
for (const num of infiniteNumbers()) {
console.log('Número:', num);
count++;
if (count >= 5) break; // 🛑 PARA quando quiser!
}
// 🔧 GERADOR INFINITO: IDs únicos
function* uniqueIdGenerator() {
let id = 1;
while (true) {
yield `id_${id++}`; // 📤 Gera IDs únicos infinitamente
}
}
// 💼 USO PRÁTICO: Sistema de IDs
const idGen = uniqueIdGenerator();
console.log('=== GERANDO IDs ÚNICOS ===');
console.log(idGen.next().value); // "id_1"
console.log(idGen.next().value); // "id_2"
console.log(idGen.next().value); // "id_3"
// 🎲 GERADOR INFINITO: Números aleatórios
function* randomNumbers(min = 0, max = 100) {
while (true) {
yield Math.floor(Math.random() * (max - min + 1)) + min;
}
}
// 🎮 SIMULAÇÃO: Dados de um jogo
console.log('=== SIMULANDO DADOS ===');
let rolls = 0;
for (const dice of randomNumbers(1, 6)) {
console.log(`Dado: ${dice}`);
if (dice === 6) {
console.log('🎉 Tirou 6! Parou o jogo.');
break;
}
rolls++;
if (rolls > 10) { // Segurança para não rodar infinito
console.log('⏰ Limite de tentativas.');
break;
}
}
// 💡 VANTAGEM: Memória constante mesmo com infinitas possibilidades!
Use quando:
Use quando:
// 🔄 EXEMPLO PRÁTICO: Processamento de logs
function* processLogs(logs) {
for (const log of logs) {
// 🔍 Analisa cada log
const parsed = {
timestamp: new Date(log.time),
level: log.level,
message: log.msg,
processed: true
};
yield parsed;
}
}
const sampleLogs = [
{ time: '2024-01-01T10:00:00Z', level: 'info', msg: 'Sistema iniciado' },
{ time: '2024-01-01T10:01:00Z', level: 'error', msg: 'Falha na conexão' },
{ time: '2024-01-01T10:02:00Z', level: 'info', msg: 'Conexão restaurada' }
];
// 🎮 CONTROLE MANUAL: Para análise cuidadosa
console.log('=== ANÁLISE MANUAL (com pausa) ===');
const logProcessor = processLogs(sampleLogs);
let logResult = logProcessor.next();
while (!logResult.done) {
const log = logResult.value;
console.log(`[${log.level.toUpperCase()}] ${log.message}`);
// ⚠️ PAUSA em erros para análise
if (log.level === 'error') {
console.log('🔴 ERRO DETECTADO! Pausando para análise...');
await new Promise(resolve => setTimeout(resolve, 2000));
}
logResult = logProcessor.next();
}
// 🔄 AUTOMÁTICO: Para processamento rápido
console.log('\n=== PROCESSAMENTO AUTOMÁTICO ===');
for (const log of processLogs(sampleLogs)) {
console.log(`✅ Processado: ${log.message}`);
}
Agora você tem controle total sobre quando e como processar dados!