Skip to content

L62 - Generadores e Iteradores

Los generadores son funciones especiales que pueden pausar su ejecución y reanudarla después, manteniendo su estado entre pausas. Se declaran con function* y usan yield para devolver valores parcialmente.

Un iterador es un objeto que sabe cómo acceder a los elementos de una colección uno a uno, mediante el método .next().

function* contador() {
yield 1;
yield 2;
yield 3;
}
const iterador = contador();
console.log(iterador.next()); // { value: 1, done: false }
console.log(iterador.next()); // { value: 2, done: false }
console.log(iterador.next()); // { value: 3, done: false }
console.log(iterador.next()); // { value: undefined, done: true }

Cada llamada a .next():

  • value: el valor yield-ado.
  • done: false si quedan más yields, true si terminó.
function* pasos() {
console.log('Paso 1: iniciar');
yield '🚀 Listo para paso 2';
console.log('Paso 2: procesar');
yield '⚙️ Listo para paso 3';
console.log('Paso 3: finalizar');
return '✅ Completado';
}
const gen = pasos();
console.log(gen.next().value); // '🚀 Listo para paso 2'
console.log(gen.next().value); // '⚙️ Listo para paso 3'
console.log(gen.next().value); // '✅ Completado'

Un objeto que implementa Symbol.iterator y puede usarse con for...of.

Un objeto con un método .next() que devuelve { value, done }.

const iterable = [1, 2, 3]; // Los arrays son iterables
const iterador = iterable[Symbol.iterator](); // Obtenemos el iterador
console.log(iterador.next()); // { value: 1, done: false }
console.log(iterador.next()); // { value: 2, done: false }
console.log(iterador.next()); // { value: 3, done: false }
console.log(iterador.next()); // { value: undefined, done: true }
function* numerosPares(limite) {
for (let i = 0; i <= limite; i += 2) {
yield i;
}
}
for (const num of numerosPares(10)) {
console.log(num); // 0, 2, 4, 6, 8, 10
}

Los generadores son iterables (tienen Symbol.iterator), por eso funcionan con for...of.

yield* delega la iteración a otro generador o iterable:

function* subNumeros() {
yield 1;
yield 2;
}
function* principal() {
yield 'Inicio';
yield* subNumeros(); // Delega a subNumeros
yield* [3, 4, 5]; // Delega a un array
yield 'Fin';
}
console.log([...principal()]);
// ['Inicio', 1, 2, 3, 4, 5, 'Fin']
function* secuenciaInfinita() {
let i = 0;
while (true) {
yield i++;
}
}
const numeros = secuenciaInfinita();
console.log(numeros.next().value); // 0
console.log(numeros.next().value); // 1
console.log(numeros.next().value); // 2
// No hay límite, pero solo generamos lo que necesitamos

Los generadores son perezosos (lazy): solo calculan el siguiente valor cuando se les pide. La secuencia infinita no ocupa memoria infinita porque solo existe en el momento de yield.

async function* paginador(urlBase) {
let pagina = 1;
let tieneMas = true;
while (tieneMas) {
const url = `${urlBase}?page=${pagina}&limit=10`;
const res = await fetch(url);
const datos = await res.json();
if (!datos.length) {
tieneMas = false;
} else {
yield datos;
pagina++;
}
}
}
// Consumir bajo demanda
for await (const pagina of paginador('/api/usuarios')) {
console.log(`Página con ${pagina.length} usuarios`);
// Procesar página...
// Podemos hacer break cuando queramos
}

.next(valor) también puede enviar valores al generador:

function* calculadora() {
const a = yield 'Dame el primer número';
const b = yield 'Dame el segundo número';
yield `Resultado: ${a + b}`;
}
const calc = calculadora();
console.log(calc.next().value); // 'Dame el primer número'
console.log(calc.next(5).value); // 'Dame el segundo número'
console.log(calc.next(3).value); // 'Resultado: 8'
const rango = {
from: 1,
to: 5,
[Symbol.iterator]() {
let current = this.from;
const last = this.to;
return {
next() {
if (current <= last) {
return { value: current++, done: false };
}
return { value: undefined, done: true };
}
};
}
};
for (const num of rango) {
console.log(num); // 1, 2, 3, 4, 5
}

Con un generador es más conciso:

const rango = {
from: 1,
to: 5,
*[Symbol.iterator]() {
for (let i = this.from; i <= this.to; i++) {
yield i;
}
}
};

🎯 Regla de oro: usa generadores cuando necesites secuencias perezosas (grandes o infinitas), flujos controlados (paginación, streams) o comunicación bidireccional con una función.