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.

  • โธ๏ธ Comportamiento bรกsico: Un generador se suspende en cada ciclo de cesiรณn y se reanuda exactamente donde se detuvo cuando se llama a next.
  • โœณ๏ธ Declaraciรณn: Un asterisco despuรฉs de la palabra clave "funciรณn" indica que la funciรณn es un generador en lugar de una funciรณn ordinaria.
  • ๐Ÿ”„ Canal bidireccional: Un valor pasado a next se convierte en el resultado de la expresiรณn yield que pausรณ la funciรณn.
  • ๐Ÿช† El infierno de las llamadas de vuelta: Las funciones de devoluciรณn de llamada anidadas se vuelven ilegibles a medida que se aรฑaden pasos, que es el problema que se pretendรญa solucionar con la adopciรณn de generadores.
  • ๐Ÿƒ Se requiere corredor: Un generador no se controla a sรญ mismo; una funciรณn de ejecuciรณn debe llamar repetidamente a next para que el trabajo asรญncrono pueda continuar.
  • ๐Ÿฆฅ Ejecuciรณn perezosa: La funciรณn de suspender y reanudar permite que un programa obtenga los valores solo en el momento en que los necesita.

Node.js Generators Comparado con las devoluciones de llamada

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.

  1. mรฉtodo de rendimiento โ€” llamada dentro de una funciรณn para detener la ejecuciรณn en la lรญnea exacta donde yield .
  2. 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.

Node.js Generators

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

  1. El primer paso es definir la funciรณn generadora. Observe el * aรฑadido a la function palabra clave. La funciรณn se llama Add y toma un parรกmetro x.
  2. El yield La 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รญ hasta next() se invoca.
  3. El segundo yield ambas pausan la funciรณn y reciben un valor: lo que sea que se pase a lo siguiente next() La llamada se convierte en el valor de y.
  4. llamar gen.next() La primera vez ejecuta la funciรณn hasta el primero. yieldLlamando gen.next(6) lo reanuda, asigna 6 a yy vuelve x + 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.

Devoluciones de llamada vs. Generators

function Timedelay(ptime, callback) {

    setTimeout(function() {

        callback("Pausing for " + ptime);

    }, ptime);   // the delay must use ptime, the parameter name
}

Code Explicaciรณn

  1. Una funciรณn llamada Timedelay se crea con un parรกmetro ptime, que contiene el nรบmero de milisegundos durante los cuales se debe hacer la pausa.
  2. setTimeout Espera 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.

Devoluciones de llamada vs. Generators

Timedelay(1000, function(message) {

    console.log(message);

    Timedelay(2000, function(message) {

        console.log(message);

        Timedelay(3000, function(message) {

            console.log(message);
        });
    });
});

Code Explicaciรณn

  1. Timedelay Se llama como una funciรณn de devoluciรณn de llamada con el valor 1000.
  2. Dentro de esa devoluciรณn de llamada, Timedelay Se vuelve a llamar con 2000.
  3. Dentro de ese, Timedelay Se 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.

Devoluciones de llamada vs. Generators

function* Messages() {
    console.log(yield Timedelay(1000));
    console.log(yield Timedelay(2000));
    console.log(yield Timedelay(3000));
}

Code Explicaciรณn

  1. Se define una funciรณn generadora para llamar a la Timedelay funciรณn.
  2. yield se usa con Timedelay y 1000 como valor del parรกmetro.
  3. yield Se 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. yield es vรกlido solo dentro de un function*.
  • El valor de next() llega indefinido: la primera next() La llamada no puede entregar un valor, porque el generador no ha alcanzado un yield Sin embargo, envรญe los datos a partir de la segunda llamada.
  • Bucle infinito en un corredor: result.done nunca se comprueba, asรญ que next() 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.

Preguntas Frecuentes

Un objeto con dos propiedades: value, conservando todo lo que se entregรณ, y done, un valor booleano que se vuelve verdadero una vez que el generador termina. Los corredores comprueban que se ha completado para saber cuรกndo detenerse.

Sรญ. Un generador que contiene un bucle infinito es seguro porque no se realiza ningรบn cรกlculo hasta que se llama a next(). Esto hace que los generadores sean muy adecuados para flujos de identificadores o resultados paginados.

Sรญ. AI Las herramientas cambian yield por await y eliminan el corredor por completo. RevExamine con atenciรณn las secuencias perezosas, porque un generador que nunca termina no tiene un equivalente directo de async await.

Asistentes de IA traces si el ejecutor dejรณ de llamar a next, si nunca se invocรณ una funciรณn de devoluciรณn de llamada de reanudaciรณn, o si nunca se llegรณ a done. Esos tres factores explican casi todos los generadores bloqueados.

No. Se declara un generador asรญncrono. async function* y estรก consumido por for await...ofCombina ambas ideas y se adapta al procesamiento de datos asรญncronos en tiempo real, como por ejemplo los cursores de bases de datos.

Resumir este post con: