Generadores en JavaScript: function*, yield e iteración | Nicolás Garzón
Una función generadora puede pausar su ejecución con yield y continuar más adelante desde el mismo punto.
JavaScript
Copiar function * createIds ( ) {
yield 1 ;
yield 2 ;
yield 3 ;
} Llamarla no ejecuta todavía su cuerpo. Crea un objeto generator , que es un iterador.
JavaScript
Copiar const ids = createIds ( ) ;
JavaScript
Copiar function * createIds ( ) {
yield 1 ;
} Es la definición reutilizable.
JavaScript
Copiar const ids = createIds ( ) ; Es una ejecución concreta con su propio estado y progreso.
JavaScript
Copiar const first = createIds ( ) ;
const second = createIds ( ) ; Cada llamada crea un objeto independiente.
JavaScript
Copiar function * example ( ) {
console. log ( "Started" ) ;
yield 1 ;
}
const generator = example ( ) ; Todavía no se imprime nada.
JavaScript
Copiar generator. next ( ) ;
La primera llamada a next inicia la ejecución y avanza hasta el primer yield, return o final de la función.
JavaScript
Copiar function * createValues ( ) {
yield "A" ;
yield "B" ;
}
Produce un valor.
Pausa la función.
Conserva sus variables locales y posición de ejecución.
Espera otra llamada a next.
JavaScript
Copiar const values = createValues ( ) ;
values. next ( ) ;
values. next ( ) ;
values. next ( ) ;
JavaScript
Copiar function * counter ( ) {
let value = 0 ;
while ( value < 3 ) {
yield value;
value++ ;
}
} JavaScript
Copiar const sequence = counter ( ) ;
sequence. next ( ) ;
sequence. next ( ) ;
sequence. next ( ) ; La variable value mantiene su estado entre pausas.
JavaScript
Copiar function * example ( ) {
yield 1 ;
return 99 ;
} JavaScript
Copiar const generator = example ( ) ;
generator. next ( ) ;
generator. next ( ) ;
El valor de return aparece en el registro final de next, pero for...of no lo incluye.
JavaScript
Copiar [ ... example ( ) ] ; yield produce elementos de la secuencia; return termina la ejecución.
JavaScript
Copiar const generator = createValues ( ) ;
generator[ Symbol. iterator] ( ) === generator;
JavaScript
Copiar for ( const value of createValues ( ) ) {
console. log ( value) ;
} JavaScript
Copiar [ ... createValues ( ) ] ; El objeto concreto se consume una sola vez.
JavaScript
Copiar const generator = createValues ( ) ;
[ ... generator] ;
[ ... generator] ; La función generadora puede llamarse otra vez para crear otro recorrido.
Una expresión yield también puede recibir el valor enviado al reanudar la ejecución.
JavaScript
Copiar function * conversation ( ) {
const name = yield "What is your name?" ;
return ` Hello, ${ name} ` ;
} JavaScript
Copiar const chat = conversation ( ) ;
chat. next ( ) ;
chat. next ( "Nicolás" ) ;
El argumento enviado a next se convierte en el resultado de la expresión yield pausada.
JavaScript
Copiar const chat = conversation ( ) ;
chat. next ( "Ignored" ) ; No existe un yield pausado que pueda recibir ese primer argumento. La primera llamada inicia la función.
Para enviar un valor, primero debes avanzar hasta un yield.
yield* delega en otro iterable.
JavaScript
Copiar function * frontend ( ) {
yield "HTML" ;
yield "CSS" ;
}
function * stack ( ) {
yield "JavaScript" ;
yield * frontend ( ) ;
yield "React" ;
} JavaScript
Copiar [ ... stack ( ) ] ;
También puede delegar a arrays, strings, Sets u otros iterables.
JavaScript
Copiar function * values ( ) {
yield * [ 1 , 2 , 3 ] ;
} JavaScript
Copiar function * inner ( ) {
yield 1 ;
return 99 ;
}
function * outer ( ) {
const finalValue = yield * inner ( ) ;
yield finalValue;
} JavaScript
Copiar [ ... outer ( ) ] ; yield* produce los valores delegados y su expresión recibe el valor final retornado por el iterador interno.
JavaScript
Copiar const generator = createValues ( ) ;
generator. next ( ) ;
generator. return ( "Stopped" ) ; Produce una finalización anticipada.
JavaScript
Copiar {
value : "Stopped" ,
done : true ,
} Después, el generator permanece cerrado.
JavaScript
Copiar function * readValues ( ) {
try {
yield 1 ;
yield 2 ;
yield 3 ;
} finally {
console. log ( "Cleanup" ) ;
}
} Si un for...of termina con break, solicita el cierre del iterador y se ejecuta finally.
JavaScript
Copiar for ( const value of readValues ( ) ) {
if ( value === 2 ) {
break ;
}
}
Esto permite liberar recursos controlados por la abstracción.
JavaScript
Copiar function * task ( ) {
try {
yield "waiting" ;
} catch ( error) {
yield ` Recovered: ${ error. message} ` ;
}
} JavaScript
Copiar const generator = task ( ) ;
generator. next ( ) ;
generator. throw ( new Error ( "Failed" ) ) ; throw inserta un error en el punto donde la función estaba pausada.
Si no existe un try...catch que lo maneje, el error se propaga y el generator termina.
JavaScript
Copiar function * naturalNumbers ( ) {
let number = 0 ;
while ( true ) {
yield number++ ;
}
} La función no construye todos los números. Produce uno por solicitud.
JavaScript
Copiar const numbers = naturalNumbers ( ) ;
numbers. next ( ) . value;
numbers. next ( ) . value; Pero una secuencia infinita sigue siendo peligrosa si el consumidor intenta terminarla por completo.
JavaScript
Copiar [ ... naturalNumbers ( ) ] ;
Tampoco evita automáticamente bloquear el hilo. Un bucle síncrono que solicite valores sin parar seguirá ejecutándose de forma síncrona.
JavaScript
Copiar const firstTen = Iterator. from (
naturalNumbers ( ) ,
) . take ( 10 ) . toArray ( ) ; JavaScript
Copiar function * range ( start, end, step = 1 ) {
if ( step === 0 ) {
throw new RangeError (
"Step cannot be zero" ,
) ;
}
if ( step > 0 ) {
for (
let value = start;
value <= end;
value += step
) {
yield value;
}
return ;
}
for (
let value = start;
value >= end;
value += step
) {
yield value;
}
} JavaScript
Copiar [ ... range ( 1 , 5 , 2 ) ] ;
El generador expresa una secuencia sin crear previamente un array intermedio.
JavaScript
Copiar function * walkTree ( node ) {
yield node;
for ( const child of node. children ?? [ ] ) {
yield * walkTree ( child) ;
}
} JavaScript
Copiar for ( const node of walkTree ( root) ) {
console. log ( node. name) ;
} La recursividad y yield* permiten describir el recorrido de forma incremental.
Para árboles muy profundos, la recursividad todavía puede alcanzar el límite del Call Stack; la pereza no elimina esa profundidad de llamadas.
JavaScript
Copiar function * mapIterable ( iterable, transform ) {
for ( const value of iterable) {
yield transform ( value) ;
}
} JavaScript
Copiar const doubled = mapIterable (
range ( 1 , 3 ) ,
( value ) => value * 2 ,
) ;
[ ... doubled] ; Los helpers modernos de Iterator ya proporcionan operaciones como map, filter, take y toArray, pero los generadores siguen siendo útiles para lógica personalizada.
Un generator conserva su contexto de ejecución mientras está pausado.
JavaScript
Copiar function createSequence ( prefix ) {
return function * sequence ( ) {
let index = 1 ;
while ( true ) {
yield ` ${ prefix} - ${ index++ } ` ;
}
} ;
} La función generadora creada conserva prefix mediante closure; cada objeto generator conserva además su propio index.
JavaScript
Copiar function * heavyWork ( ) {
while ( true ) {
yield expensiveSynchronousCalculation ( ) ;
}
} Cada next() ejecuta el trabajo en el hilo actual hasta llegar al siguiente yield.
Un generator organiza la pausa cooperativa; no crea otro hilo, no paraleliza y no delega automáticamente el trabajo al Event Loop.
JavaScript
Copiar async function * readPages ( ) {
let page = 1 ;
while ( true ) {
const response = await fetchPage ( page) ;
if ( response. items. length === 0 ) {
return ;
}
yield response. items;
page++ ;
}
} JavaScript
Copiar for await ( const items of readPages ( ) ) {
render ( items) ;
} Un async generator produce un iterador asíncrono: sus operaciones next retornan promesas.
Se profundizará junto con for await...of, streams y asincronía.
JavaScript
Copiar function * readFileLines ( reader ) {
try {
while ( true ) {
const line = reader. readLine ( ) ;
if ( line === null ) {
return ;
}
yield line;
}
} finally {
reader. close ( ) ;
}
} Mientras el generator permanezca pausado y accesible, también puede mantener accesibles sus variables y recursos.
El consumidor debe terminarlo o permitir que deje de ser accesible. Para recursos críticos, diseña limpieza explícita con return, finally o APIs de disposición.
JavaScript
Copiar function * chunk ( items, size ) {
if ( ! Number. isInteger ( size) || size <= 0 ) {
throw new RangeError (
"Chunk size must be positive" ,
) ;
}
for (
let index = 0 ;
index < items. length;
index += size
) {
yield items. slice ( index, index + size) ;
}
} JavaScript
Copiar for ( const batch of chunk ( products, 100 ) ) {
await saveBatch ( batch) ;
} La producción de lotes es síncrona; el consumidor decide esperar cada operación asíncrona.
Confundir la función generadora con el objeto generator.
Esperar que el cuerpo se ejecute al llamar la función.
Pensar que yield termina la función como return.
Enviar un valor en el primer next y esperar recibirlo dentro.
Reutilizar un generator ya consumido.
Expandir una secuencia infinita.
Creer que la evaluación perezosa evita bloquear el hilo.
Presentar generadores como concurrencia o paralelismo.
Olvidar cerrar recursos ante una salida anticipada.
Suponer que el valor final de return aparece en for...of.
Pensar que yield* solo funciona con otros generadores.
function* define una función generadora.
Llamarla crea un objeto generator sin ejecutar todavía el cuerpo.
next avanza hasta el siguiente yield.
Las variables locales conservan su estado entre pausas.
El generator es un iterable iterator de un solo recorrido.
next(value) envía un valor al yield pausado.
yield* delega en cualquier iterable.
return y throw permiten terminar o inyectar errores desde el consumidor.
finally permite limpieza ante cierre anticipado.
La pereza no implica asincronía ni otro hilo.
¿Qué valores devuelve cada llamada?
JavaScript
Copiar function * example ( ) {
const value = yield "A" ;
return value;
}
const generator = example ( ) ;
generator. next ( "ignored" ) ;
generator. next ( "B" ) ; Respuesta La primera llamada devuelve { value: "A", done: false }; su argumento se ignora porque todavía no había un yield pausado. La segunda envía "B" al yield y devuelve { value: "B", done: true } por el return.
TypedArrays y datos binarios explica cómo JavaScript representa bytes y valores numéricos dentro de buffers de memoria.