Funciones generadoras en JavaScript
Aprende los generadores de JavaScript: sintaxis function*, yield y yield*, envío de valores con next(), return() y throw(), secuencias infinitas y cómo los generadores potencian los iterables personalizados.
Un generador es un tipo especial de función que puede pausarse a sí misma en medio de su ejecución, devolver un valor al llamador y luego reanudar exactamente donde lo dejó la próxima vez que se le pide más. En lugar de calcular todo de antemano y retornar una sola vez, un generador produce una secuencia de valores de forma perezosa: uno a la vez, solo cuando se solicita.
Esta única idea hace que los generadores sean la forma más limpia en JavaScript de construir iterables personalizados, modelar secuencias infinitas sin quedarse sin memoria y recorrer lógica de larga duración bajo demanda. Esta guía cubre la sintaxis function*, yield y yield*, la comunicación bidireccional con next(), la terminación anticipada y los patrones prácticos donde los generadores brillan.
Esta página se basa en iterables e iteradores — si el protocolo de iteradores es nuevo para ti, léelo primero.
Comprender las funciones generadoras
Los fundamentos de las funciones generadoras
Una función generadora se declara como una función normal pero con un asterisco después de la palabra clave function: function*. El asterisco es lo que marca la función como un generador.
Llamar a una función generadora no ejecuta su cuerpo. En cambio, devuelve un objeto generador — un iterador que se controla manualmente. Cada llamada a su método next() ejecuta el cuerpo hasta el siguiente yield, luego se pausa y devuelve { value, done }:
El primer next() se ejecuta hasta el primer yield 'Hello' y se pausa allí. El segundo reanuda después de esa línea y se ejecuta hasta yield 'World'. El tercero reanuda, no encuentra más sentencias yield, llega al final de la función e informa done: true. Llamar a next() nuevamente seguiría devolviendo { value: undefined, done: true }.
Dado que un objeto generador también es iterable, puedes consumirlo con for...of, el operador spread o la desestructuración — todos los cuales se detienen automáticamente cuando done se vuelve true:
Un objeto generador solo se puede iterar una vez. Una vez agotado, for...of no produce nada. Llama a la función generadora de nuevo para obtener un generador nuevo.
El valor de return de un generador
Un return dentro de un generador termina la iteración y proporciona el value final junto con done: true. Ten en cuenta que for...of ignora este valor retornado — solo ve los valores emitidos con yield:
Controlar el flujo con return() y throw()
Además de next(), un objeto generador expone dos métodos más que permiten al llamador dirigir la ejecución desde el exterior:
generator.return(value)obliga al generador a terminar de inmediato, ejecutando cualquier bloquefinallyde salida y devolviendo{ value, done: true }.generator.throw(error)inyecta una excepción en el puntoyieldactual, para que el generador pueda capturarla contry...catch— o propagarla si no lo hace.
El bloque finally hace que los generadores sean un lugar ordenado para liberar recursos (cerrar un archivo, desconectar un stream) incluso cuando la iteración se abandona antes de tiempo.
Patrones avanzados de generadores
Delegación con yield*
Cuando un generador necesita producir los valores de otro generador o de cualquier iterable, usa yield* (léase "yield-delegate") en lugar de escribir un bucle manual. Reenvía de forma transparente cada valor de la fuente delegada:
Un detalle útil: el valor de la expresión de yield* es el valor de return del generador interno, lo que permite componer generadores y pasar un resultado de vuelta al externo:
Enviar valores a los generadores
La comunicación con un generador es bidireccional. Una expresión yield no solo envía un valor hacia afuera mediante next(), sino que también se evalúa como lo que pases hacia adentro en la siguiente llamada next(value). Esto convierte a un generador en una pequeña corrutina interactiva:
El primer next() se ejecuta hasta el primer yield y devuelve la pregunta; su argumento se descarta porque todavía no hay ningún yield pausado para recibirlo. El segundo next('Alice') reanuda el yield pausado, de modo que la expresión se convierte en 'Alice' y se asigna a name.
Aplicaciones prácticas de los generadores en JavaScript
Construir iterables personalizados
El uso más común de los generadores en el mundo real es hacer que tus propios objetos sean iterables. Define [Symbol.iterator] como un método generador, y cualquier for...of, spread o desestructuración funcionará con el objeto. Esto implica mucho menos código repetitivo que escribir un objeto iterador con un next() a mano (consulta tipo Symbol para ver qué es Symbol.iterator):
Secuencias perezosas e infinitas
Como un generador calcula cada valor solo cuando se le pide, puede describir una secuencia que sea conceptualmente infinita sin agotar nunca la memoria. El ejemplo clásico es un contador, pero el mismo enfoque sirve para rangos, generadores de IDs o flujos de Fibonacci. Se combina con un helper "take" que se detiene después de la cantidad que necesitas:
Un generador de rango parametrizado es una variante útil y reutilizable de la misma idea:
Gestionar operaciones asíncronas
Históricamente, los generadores se usaban para aplanar el "callback hell": cada yield se pausaba en una Promise, y un ejecutor externo reanudaba el generador una vez que esa Promise se resolvía. El ejemplo a continuación muestra la mecánica manual para que puedas ver lo que ocurre internamente:
Este es un ejecutor manual simplificado. En código moderno escribirías esto con async/await, que el lenguaje construyó directamente sobre este patrón de generador más Promise. Para la iteración asíncrona (flujo de fragmentos en el tiempo) usa generadores async con async function* y for await...of.
Generadores vs. funciones regulares
| Aspecto | Función regular | Función generadora |
|---|---|---|
| Declaración | function fn() | function* fn() |
| Al llamarla | ejecuta el cuerpo hasta completarlo | devuelve un objeto generador, no ejecuta nada aún |
| Retorna | un único valor | una secuencia de valores mediante yield |
| ¿Puede pausarse? | no | sí, en cada yield |
| ¿Es iterable? | no | sí (funciona con for...of, spread) |
Usa un generador cuando necesites valores producidos de forma perezosa o bajo demanda, que un objeto sea iterable, o una secuencia que pueda ser infinita. Para un cálculo simple de una sola ejecución, una función regular es más sencilla y rápida.
Conclusión
Los generadores le dan a JavaScript una forma limpia de producir secuencias de manera perezosa, pausar y reanudar la lógica, y hacer que cualquier objeto funcione con bucles como for...of. Domina yield para emitir valores, yield* para delegar a otros iterables, y next()/return()/throw() para dirigir y controlar el flujo — y tendrás una herramienta que impulsa todo, desde iterables personalizados hasta la sintaxis async/await construida sobre ella.