Node.js Generators y comparar con devoluciones de llamada
โก Resumen inteligente
GeneratorEn Node.js, las funciones se pueden suspender y reanudar bajo demanda. La palabra clave `yield` pausa la funciรณn, el mรฉtodo `next` la reanuda, y esta combinaciรณn simplifica las funciones de devoluciรณn de llamada anidadas, convirtiรฉndolas en cรณdigo secuencial legible.

En este tutorial analizaremos los generadores y en quรฉ se diferencian de las funciones de devoluciรณn de llamada.
ยฟQuรฉ son los generadores?
Generators se hizo popular en Node.js por lo que son capaces de hacer.
- GeneratorLas s son ejecuciones de funciones que pueden suspenderse y reanudarse en un momento posterior.
- GeneratorLas funciones son รบtiles para conceptos como la "ejecuciรณn diferida". Al suspender la ejecuciรณn y reanudarla cuando se desee, los valores se obtienen solo cuando son necesarios.
Generators tienen los dos mรฉtodos clave que se describen a continuaciรณn.
- mรฉtodo de rendimiento โ llamada dentro de una funciรณn para detener la ejecuciรณn en la lรญnea exacta donde
yield. - Siguiente mรฉtodo โ llamada desde la aplicaciรณn principal para reanudar una funciรณn que ha cedido el control. La ejecuciรณn continรบa hasta la siguiente
yieldo hasta el final de la funciรณn.
Veamos un ejemplo de cรณmo se pueden usar los generadores. El ejemplo define una funciรณn de suma simple que suma dos nรบmeros, deteniendo la ejecuciรณn en diferentes puntos para mostrar cรณmo se comportan los generadores.
function* Add(x) { yield x + 1; // The value passed into the second next() call lands here var y = yield(null); return x + y; } var gen = Add(5); gen.next(); // runs to the first yield, returns { value: 6, done: false } gen.next(6); // resumes, y becomes 6, returns { value: 11, done: true }
Code Explicaciรณn
- El primer paso es definir la funciรณn generadora. Observe el
*aรฑadido a lafunctionpalabra clave. La funciรณn se llamaAddy toma un parรกmetrox. - El
yieldLa palabra clave es especรญfica de los generadores y pausa la funciรณn a mitad de la ejecuciรณn. La ejecuciรณn se detiene aquรญ hastanext()se invoca. - El segundo
yieldambas pausan la funciรณn y reciben un valor: lo que sea que se pase a lo siguientenext()La llamada se convierte en el valor dey. - llamar
gen.next()La primera vez ejecuta la funciรณn hasta el primero.yieldLlamandogen.next(6)lo reanuda, asigna 6 ayy vuelvex + y.
โ ๏ธ Correcciรณn: El listado original contenรญa la lรญnea y = 6 inmediatamente despues var y = yield(null);Esa asignaciรณn sobrescribe cualquier valor. next() suministros, lo cual frustra el propรณsito del ejemplo y hace que el segundo yield sin sentido. El valor ahora se pasa a travรฉs de gen.next(6), que es como un generador recibe datos del emisor.
Callbacks frente a generadores
Generators resuelve el problema conocido como infierno de las devoluciones de llamada. Las funciones de devoluciรณn de llamada a veces se anidan tan profundamente durante el desarrollo de una aplicaciรณn Node.js que son difรญciles de seguir.
Aquรญ es donde los generadores resultan รบtiles. Uno de los ejemplos mรกs comunes es la creaciรณn de funciones de temporizador. El siguiente ejemplo crea una funciรณn de retardo simple y luego la llama con retardos de 1000, 2000 y 3000 ms.
Paso 1) Defina la funciรณn de devoluciรณn de llamada con el cรณdigo de retardo de tiempo necesario.
function Timedelay(ptime, callback) { setTimeout(function() { callback("Pausing for " + ptime); }, ptime); // the delay must use ptime, the parameter name }
Code Explicaciรณn
- Una funciรณn llamada
Timedelayse crea con un parรกmetroptime, que contiene el nรบmero de milisegundos durante los cuales se debe hacer la pausa. setTimeoutEspera esa cantidad de milisegundos y luego invoca la funciรณn de devoluciรณn de llamada proporcionada.
โ ๏ธ Correcciรณn: El cรณdigo original pasรณ time como el segundo argumento a setTimeout, pero ninguna variable llamada time existe โ el parรกmetro es ptimeTal como estรก escrito, arroja ReferenceError: time is not defined.
Paso 2) Ahora consideremos el cรณdigo que utiliza funciones de devoluciรณn de llamada. Para encadenar retrasos de 1000, 2000 y 3000 milisegundos, las funciones de devoluciรณn de llamada se anidan unas dentro de otras.
Timedelay(1000, function(message) { console.log(message); Timedelay(2000, function(message) { console.log(message); Timedelay(3000, function(message) { console.log(message); }); }); });
Code Explicaciรณn
TimedelaySe llama como una funciรณn de devoluciรณn de llamada con el valor 1000.- Dentro de esa devoluciรณn de llamada,
TimedelaySe vuelve a llamar con 2000. - Dentro de ese,
TimedelaySe llama por tercera vez con 3000.
โ ๏ธ Correcciรณn: cada devoluciรณn de llamada en el registro original msg, pero el parรกmetro se llama messageLas tres lรญneas lanzaron ReferenceError: msg is not defined.
Como se puede observar en el cรณdigo anterior, la situaciรณn se complica con cada llamada adicional; este anidamiento es lo que describe el "infierno de las devoluciones de llamada".
Paso 3) Ahora veamos el mismo comportamiento escrito con generadores. La secuencia se lee como tres lรญneas planas en lugar de tres niveles de anidamiento.
function* Messages() { console.log(yield Timedelay(1000)); console.log(yield Timedelay(2000)); console.log(yield Timedelay(3000)); }
Code Explicaciรณn
- Se define una funciรณn generadora para llamar a la
Timedelayfunciรณn. yieldse usa conTimedelayy 1000 como valor del parรกmetro.yieldSe vuelve a usar con 2000, y una vez mรกs con 3000.
โ ๏ธ Correcciรณn: El original escribiรณ console,log(...) con una coma en lugar de un punto en las tres lรญneas, lo cual es un error de sintaxis e impide que el archivo se analice correctamente.
Cรณmo ejecutar un Generator con funciรณn de corredor
El generador anterior se lee de maravilla, pero no harรก nada por sรญ solo. Este es el detalle que la mayorรญa de las explicaciones omiten, y es la razรณn por la que la versiรณn plana puede parecer magia. Un generador es una mรกquina en pausa, y algo tiene que seguir presionando el botรณn. Llamando Messages() Simplemente crea el objeto generador y regresa inmediatamente; nunca se inicia ningรบn temporizador y no se imprime nada.
La pieza que falta es un corredor: una pequeรฑa funciรณn que llama next()espera a que finalice el trabajo asรญncrono y luego llama next() de nuevo con el resultado.
// Timedelay now hands its result back to the runner function Timedelay(ptime) { return function(resume) { setTimeout(function() { resume("Pausing for " + ptime); }, ptime); }; } // The runner drives the generator to completion function run(generatorFunction) { var it = generatorFunction(); function step(value) { var result = it.next(value); if (result.done) return; // result.value is the function returned by Timedelay result.value(step); } step(); } run(Messages);
Tres puntos hacen que esto funcione. Primero, Timedelay Ya no acepta una devoluciรณn de llamada directamente; devuelve una funciรณn que el ejecutor puede invocar, devolviendo el control cuando se activa el temporizador. Segundo, it.next(value) alimenta el resultado anterior al generador en pausa, que es lo que permite yield aparecer en el lado derecho de una asignaciรณn. Tercero, el corredor se detiene tan pronto como result.done se vuelve verdadero, por lo que la recursiรณn termina naturalmente. Este patrรณn de corredor es exactamente lo que proporcionaron bibliotecas como co, y es el mecanismo que async/await Posteriormente, fue absorbido por el propio idioma.
Generators vs async/await: ยฟCuรกl usar?
GeneratorLas s eran la forma estรกndar de escribir cรณdigo asรญncrono plano antes async/await existรญan, y ambos comparten la misma idea subyacente de pausar una funciรณn a mitad de camino. Node ha sido compatible async/await De forma nativa desde la versiรณn 7.6, lo que cambia la recomendaciรณn para cualquiera que comience un nuevo trabajo hoy.
| Aspecto | Generators | async / await |
|---|---|---|
| Se necesita corredor | Sรญ, escrito por usted o por una biblioteca. | No, estรก integrado en el lenguaje. |
| Palabra clave de pausa | yield |
await |
| Manejo de errores | El corredor debe reenviar los errores | Intentar atrapar lo normal |
| Puede hacer una pausa a mitad de camino. | Sรญ, y puede reanudarse de forma selectiva. | No, se ejecuta hasta completarse |
| Mejores para | Secuencias perezosas y flujos infinitos | Trabajo asรญncrono ordinario |
Usar async/await Para el flujo asรญncrono en cรณdigo nuevo, ya que elimina el ejecutor y restaura el try y catch habituales. Mantenga los generadores para lo que solo ellos hacen bien: producir valores de forma diferida, iterar secuencias grandes o infinitas y cualquier caso en el que el consumidor decida exactamente cuรกndo se calcula el siguiente valor.
Preguntas frecuentes sobre bancarrota Generator Errores y cรณmo solucionarlos
La mayorรญa de los problemas con los generadores provienen de olvidar que la funciรณn es inerte hasta que algo la llama. next()o bien, debido a pequeรฑos errores sintรกcticos alrededor del asterisco. Cada sรญntoma que aparece a continuaciรณn indica su causa y su soluciรณn.
- No ocurre nada cuando se llama al generador: Llamarla devuelve un iterador, no un resultado. Llamar
next()o pasรกrselo a un corredor. - SyntaxError en yield: Falta el asterisco.
yieldes vรกlido solo dentro de unfunction*. - El valor de next() llega indefinido: la primera
next()La llamada no puede entregar un valor, porque el generador no ha alcanzado unyieldSin embargo, envรญe los datos a partir de la segunda llamada. - Bucle infinito en un corredor:
result.donenunca se comprueba, asรญ quenext()Se sigue llamando incluso despuรฉs de que termine. Siempre regresa cuando done sea verdadero. - Los errores desaparecen silenciosamente: Un rechazo dentro del paso asรญncrono nunca se devuelve al generador. Reenvรญelo con
it.throw(err)para que un bloque try-catch escrito dentro del generador pueda verlo y manejarlo.




