La gu铆a avanzada de JavaScript: generadores. Parte 2, un caso de uso simple



El comportamiento de los generadores descrito en el art铆culo anterior no es complejo, pero definitivamente sorprende y puede parecer confuso al principio. Entonces, en lugar de aprender nuevos conceptos, ahora haremos una pausa y veremos un ejemplo interesante de uso de generadores.



Tengamos una funci贸n como esta:



function maybeAddNumbers() {
    const a = maybeGetNumberA();
    const b = maybeGetNumberB();

    return a + b;
}

      
      





Funciones maybeGetNumberA



y maybeGetNumberB



n煤meros de retorno, pero a veces pueden devolver null



o undefined



. Esto se evidencia con la palabra "tal vez" en sus nombres. Si esto sucede, no intente poner estos valores (por ejemplo, el n煤mero y null



), es mejor detenerse y regresar, digamos null



. Es decir null



, y no alg煤n valor impredecible obtenido al sumar null



/ undefined



con un n煤mero u otro null



/ undefined



.



Por lo tanto, debe verificar que los n煤meros est茅n realmente definidos:



function maybeAddNumbers() {
    const a = maybeGetNumberA();
    const b = maybeGetNumberB();

    if (a === null || a === undefined || b === null || b === undefined) {
        return null;
    }

    return a + b;
}

      
      





Todo funciona, pero si a



es null



o undefined



, entonces no tiene sentido llamar a la funci贸n maybeGetNumberB



. Sabemos que ser谩 devuelto de todos modos null



.



Reescribamos la funci贸n:



function maybeAddNumbers() {
    const a = maybeGetNumberA();

    if (a === null || a === undefined) {
        return null;
    }

    const b = maybeGetNumberB();

    if (b === null || b === undefined) {
        return null;
    }

    return a + b;
}

      
      





Entonces. En lugar de tres simples l铆neas de c贸digo, lo ampliamos r谩pidamente a 10 l铆neas (sin contar las vac铆as). Y ahora se aplican las funciones if



, que debe recorrer para comprender qu茅 hace la funci贸n. 隆Y esto es solo un ejemplo educativo! Imagine una base de c贸digo real con una l贸gica mucho m谩s compleja, lo que dificulta a煤n m谩s las comprobaciones. Me gustar铆a usar generadores aqu铆 y simplificar el c贸digo.



Echar un vistazo:



function* maybeAddNumbers() {
    const a = yield maybeGetNumberA();
    const b = yield maybeGetNumberB();

    return a + b;
}

      
      





驴Y si pudi茅ramos dejar que la expresi贸n yield <smething>



pruebe si es un <smething>



valor real y no null



o undefined



? Si resulta que no es un n煤mero, simplemente nos detenemos y regresamos null



, como en la versi贸n anterior del c贸digo.



Es decir, se puede escribir c贸digo que se ve como que s贸lo trabaja con valores reales, definidas. 隆El generador puede verificar esto y tomar las medidas adecuadas para usted! Magia, 驴verdad? Y no solo es posible, 隆es f谩cil de escribir!



Por supuesto, los propios generadores no tienen esta funcionalidad. Simplemente devuelven iteradores, y puede insertar valores nuevamente en los generadores si lo desea. As铆 que tenemos que escribir un contenedor, que as铆 sea runMaybe



.



En lugar de llamar a la funci贸n directamente:



const result = maybeAddNumbers();

      
      





lo llamaremos como un argumento contenedor:



const result = runMaybe(maybeAddNumbers());

      
      





Este patr贸n es muy com煤n en generadores. Por s铆 mismos, no saben mucho, pero con la ayuda de envoltorios escritos por ellos mismos, 隆puede darles a los generadores el comportamiento deseado! Eso es lo que necesitamos ahora.



runMaybe



- una funci贸n que toma un argumento: un iterador creado por el generador:



function runMaybe(iterator) {

}

      
      





Ejecutemos este iterador en un bucle while



. Para hacer esto, debe llamar al iterador por primera vez y comenzar a verificar su propiedad done



:



function runMaybe(iterator) {
    let result = iterator.next();

    while(!result.done) {

    }
}

      
      





Dentro del bucle, tenemos dos posibilidades. Si result.value



es null



o undefined



, la iteraci贸n debe detenerse inmediatamente y devolverse null



. Hag谩moslo:



function runMaybe(iterator) {
    let result = iterator.next();

    while(!result.done) {
        if (result.value === null || result.value === undefined) {
            return null;
        }
    }
}

      
      





Aqu铆, return



detenemos inmediatamente la iteraci贸n con ayuda y regresamos del contenedor null



. Pero si result.value



es un n煤mero, entonces necesita "regresar" al generador. Por ejemplo, si la yield maybeGetNumberA()



funci贸n maybeGetNumberA()



es un n煤mero, entonces necesita reemplazar el yield maybeGetNumberA()



valor de ese n煤mero. D茅jame explicarte: digamos que el resultado del c谩lculo maybeGetNumberA()



es 5, luego reemplazamos const a = yield maybeGetNumberA();



con const a = 5;



. Como puede ver, no necesitamos cambiar el valor extra铆do, es suficiente con pasarlo de nuevo al generador.



Recordamos que puede reemplazar yield <smething>



con alg煤n valor pas谩ndolo como argumento al m茅todo next



en un iterador:



function runMaybe(iterator) {
    let result = iterator.next();

    while(!result.done) {
        if (result.value === null || result.value === undefined) {
            return null;
        }

        // we are passing result.value back
        // to the generator
        result = iterator.next(result.value)
    }
}

      
      





Como puede ver, el nuevo resultado ahora se almacena nuevamente en una variable result



. Esto es posible porque declaramos espec铆ficamente el result



uso de let



.



Ahora, si el generador encuentra un null



/ al recuperar un valor undefined



, simplemente regresamos null



del contenedor runMaybe



.



Queda por a帽adir algo m谩s para que el proceso de iteraci贸n finalice sin detectar null



/ undefined



. Despu茅s de todo, si obtenemos dos n煤meros, 隆entonces debemos devolver su suma del contenedor!



El generador maybeAddNumbers



termina con una expresi贸n return



. Entendemos que la presencia return <smething>



en el generador hace que devuelva next



un objeto de la llamada { value: <smething>, done: true }



. Cuando esto sucede, el ciclo se while



detiene porque la propiedad done



obtiene un valor true



. 隆Pero el 煤ltimo valor devuelto (en nuestro caso particular, esto a + b



) a煤n se almacenar谩 en la propiedad result.value



! Y podemos simplemente devolverlo:



function runMaybe(iterator) {
    let result = iterator.next();

    while(!result.done) {
        if (result.value === null || result.value === undefined) {
            return null;
        }

        result = iterator.next(result.value)
    }

    // just return the last value
    // after the iterator is done
    return result.value;
}

      
      





隆Y es todo!



Creemos funciones maybeGetNumberA



y maybeGetNumberB



, y dejemos que devuelvan n煤meros reales primero:



const maybeGetNumberA = () => 5;
const maybeGetNumberB = () => 10;

      
      





Ejecutemos el c贸digo y registremos el resultado:



function* maybeAddNumbers() {
    const a = yield maybeGetNumberA();
    const b = yield maybeGetNumberB();

    return a + b;
}

const result = runMaybe(maybeAddNumbers());

console.log(result);

      
      





Como era de esperar, aparecer谩 en la consola el n煤mero 15.



Ahora, reemplace uno de los t茅rminos con null



:



const maybeGetNumberA = () => null;
const maybeGetNumberB = () => 10;

      
      





Al ejecutar el c贸digo, obtenemos null



!



Sin embargo, es importante para nosotros asegurarnos de que maybeGetNumberB



no se llame a la funci贸n si maybeGetNumberA



devuelve null



/ undefined



. Comprobemos nuevamente que el c谩lculo fue exitoso. Para hacer esto, simplemente agregue a la segunda funci贸n console.log



:



const maybeGetNumberA = () => null;
const maybeGetNumberB = () => {
    console.log('B');
    return 10;
}

      
      





Si hemos escrito correctamente el contenedor runMaybe



, cuando se ejecute este c贸digo, la letra B



no aparecer谩 en la consola.



Efectivamente, al ejecutar el c贸digo, veremos simplemente null



. Esto significa que la envoltura realmente detiene el generador tan pronto como detecta null



/ undefined



.



El c贸digo funciona seg煤n lo previsto: produce null



cualquier combinaci贸n:



const maybeGetNumberA = () => undefined;
const maybeGetNumberB = () => 10;
const maybeGetNumberA = () => 5;
const maybeGetNumberB = () => null;
const maybeGetNumberA = () => undefined;
const maybeGetNumberB = () => null;

      
      





Etc.



Pero el beneficio de este ejemplo no radica en la ejecuci贸n de este c贸digo en particular. Se basa en el hecho de que hemos creado un contenedor universal que puede funcionar con cualquier generador que pueda extraer valores null



/ undefined



.



Escribamos una funci贸n m谩s compleja:



function* maybeAddFiveNumbers() {
    const a = yield maybeGetNumberA();
    const b = yield maybeGetNumberB();
    const c = yield maybeGetNumberC();
    const d = yield maybeGetNumberD();
    const e = yield maybeGetNumberE();
    
    return a + b + c + d + e;
}

      
      





隆Puedes hacerlo en nuestra envoltura sin ning煤n problema runMaybe



! De hecho, ni siquiera le importa al contenedor que nuestras funciones devuelvan n煤meros. Despu茅s de todo, no mencionamos el tipo num茅rico. Por lo tanto, puede usar cualquier valor en el generador (n煤meros, cadenas, objetos, matrices, estructuras de datos m谩s complejas) 隆y funcionar谩 con nuestro contenedor!



Esto es lo que inspira a los desarrolladores. Los generadores le permiten agregar funcionalidad personalizada a su c贸digo, que parece muy com煤n (aparte de las llamadas, por supuesto yield



). Solo necesita crear un contenedor que repita el generador de una manera especial. Por lo tanto, el contenedor agrega la funcionalidad necesaria al generador, 隆que puede ser cualquier cosa! Los generadores tienen posibilidades casi ilimitadas, se trata de nuestra imaginaci贸n.



All Articles