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: