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.