Deteniendo el tiempo con JS Generators

Referencia Jojo’s — Star Platinum, The world, Stop the time

Los desarrolladores JS estamos acostumbrados a 2 cosas: El dolor de escribir código asíncrono limpio y que no hay manera de detener la ejecución de una función.

Esto quedó en el pasado gracias a la llegada de una nueva feature llamada:

Generators

Los generators son básicamente funciones que pueden pausar y reanudar su ejecución mediante un iterador, esto forma de ejecutar código nos brinda la posibilidad pausar y reanudar la ejecución de nuestro código ya sea síncrono o asíncrono.

Meme el precio de la historia, No lo sé Rick, parece falso

Parece mentira pero no lo es, para empezar a entender lo que son los generators, debemos usarlos, para ello voy crear un ejemplo muy simple para mostrar la sintaxis de un generator (todos los ejemplos se encuentran aquí).

//Declaring generator function
function * helloWorldGenerators () {
console.log('Hello World')
}
// Getting generator
const iterator = helloWorldGenerators()
console.log('Before Hello World')// Running function
iterator.next()
console.log('After Hello World')

Como podemos observar las mas notables diferencias entre una función normal y una generadora (aparte de que una puede pausarse y otra no) son:

  • La forma de declaración (el * entre la palabra function y el paréntesis para el paso de parámetros).
  • Al ejecutarse la función generadora siempre te regresa un iterador, el cuerpo de la función no se ejecuta.
  • Para que el cuerpo de ejecución se ejecute tenemos que obtener el iterador e invocar el método next (Puedes observar el código completo de este ejemplo aquí).

Bueno hemos dado nuestro primer paso para entender los generators, pero aún nos falta mucho, ahora debemos detener el tiempo.

¿Deteniendo el tiempo ? Si, detener el tiempo

Para ello realizaremos un ejemplo simple que utilice la palabra reservada yield

function * stopTheTime () {
console.log('Generator, stop the time')
yield
console.log('The time is running again')
}
const iterator = stopTheTime()
iterator.next()
console.log('The time was stopped for the yield')
console.log('The time is stopped only for the generator function')
iterator.next()

Si seguimos el flujo de la función podemos ver que al ejecutar el método next(), empieza a ejecutar el cuerpo de nuestra función e imprime el primer log pero no el segundo, ya que yield detiene la ejecución dentro de la función pero fuera de ella nuestro código continua ejecutándose(Puedes observar el código completo de este ejemplo aquí).

Como pueden ver al usar yield podemos indicar en que puntos de nuestra función deseamos interrumpir su ejecución y reanudarla ejecutando el método next del iterador.

Es la magia de los generators, esto es solo una pequeña muestra de lo que pueden hacer, vamos a nuestro tercer ejemplo en el cual les mostraré como no solo podemos iniciar/pausar/reanudar, también podemos pasar valores y obtener valores de vuelta usado el método next.

function * sayHi () {
const name = yield 'You need pass a name to the next iteration'
console.log(`Hi ${name}`)
}
const iterator = sayHi()
const resultIteration = iterator.next()
console.log(resultIteration)
iterator.next('Joel')

Como podemos observar el primer next solo ejecuta la función hasta donde es detenido por el yield, este nos devuelve un objeto iterator que contiene el valor que esta después de yield (Si hubiera mas valores después de yield pueden utilizar un paréntesis para encerrar a yield y lo que será devuelto), dentro de un objeto en su propiedad value y su propiedad done en false.

El segundo next lo que hará es reanudar la ejecución de la función y “sustituirá” el yield (donde se pausó) con aquello que le sea pasado como parámetro, continuará con la ejecución hasta encontrar el siguiente yield o terminar de ejecutar el cuerpo de la función (Puedes observar el código completo de este ejemplo aquí).

Nota: Si la función no se detiene hasta terminar su ejecución el iterador regresará un objeto con el atributo done en true y el value con el valor que la función regrese usando return.

Bueno ahora que ya sabemos sumar 2 + 2 ya podemos calcular la densidad del sol. Una de las ventajas de usar iterators es que podemos iterar sobre le ejecución de una función y al momento de que esta termine de ejecutarse el iterador devolverá un objeto con la propiedad done en true. Esto nos permite controlar la ejecución de una función. Para observar esto haremos un ejemplo donde utilicemos un while para controlar la iteración de nuestro generator y una variable donde guardemos el último valor que ha devuelto el iterador.

function * sumGenerator () {
let x = yield 10
let y = yield 10
return x + y
}
function executor (generator) {
const iterator = generator()
let lastIteration = {}
while (!lastIteration.done) {
lastIteration = iterator.next(lastIteration.value)
}
console.log(lastIteration)
}
executor(sumGenerator)

La función sumGenerator simplemente va a sumar 2 números los cuales vamos a pasar mediante el método next, la parte mas importante de este ejemplo es la función executor, la cual recibe como parámetro una función generadora, obtiene un iterador y posteriormente itera con ella usando un while, mientras la propiedad done de la iteración sea falsa o no este presente el código dentro de while se continuará ejecutando hasta que la ejecución de la función generadora haya terminado (Puedes observar el código completo de este ejemplo aquí).

Meme Gallo Claudio, Matemáticas hijo

Finalmente llegamos a la parte más interesante de este post, escribir código que parezca sincrono pero manejando asíncronia. Esto lo podemos hacer con async/await pero también con generators, haciendo nuestro código mas legible y que podamos manejable.

Meme Ludoviko P. Luche, eso si me interesa

En este ejemplo voy a crear 2 cosas, una función que simule una petición, una función que ejecute esa función y sirva para escribir código asíncrono como si fuera síncrono y para finalizar una función constructora que cree objetos que se encarguen de administrar las llamadas a mis generators.

function * getResources () {
console.log('Fetch started')
const response = yield fakeFetch('')
console.log('response', response)
console.log('Fetch finished')
}
function GeneratorRunner (gen, ...parameters) {
this.it = gen(parameters)
this.handleError = this.handleError.bind(this)
this.handleResult = this.handleResult.bind(this)
this.runNext = this.runNext.bind(this)
}
GeneratorRunner.prototype.handleResult = function (nextIteration) {
if (nextIteration.done) {
return nextIteration.value
} else {
return Promise.resolve(nextIteration.value)
.then(
this.runNext,
this.handleError
)
}}
GeneratorRunner.prototype.runNext = function (value) {
const nextIteration = this.it.next(value)
return this.handleResult(nextIteration)
}
const generatorGetResources = new GeneratorRunner(getResources)
generatorGetResources.runNext()

A la función que hara la petición la llame getResources, como pueden ver hago uso yield donde hay una llamada asíncrona y debo que esperar hasta que se resuelva para poder continuar. Nota: Esto no funciona con callbacks

A la función constructora le llamé GeneratorRunner, las principales funciones de este objeto son hacer la siguiente iteración usando la función runNext y manejar el resultado de esa iteración usando la función handleResult.

Al ver el constructor de GeneratorRunner podemos ver que podemos pasar parámetros al momento de obtener nuestro iterator y al ejecutar el método runNext vemos que iniciamos la iteración y manejamos el resultado, el cual puede ser cualquier valor hasta una promesa (Puedes observar el código completo de este ejemplo aquí).

Puedes probar la función y ver otros detalles, este tema de los generators es interesante, en otros posts hablaremos sobre la delegación de iteradores, para hacer aplicaciones mas complejas. El código de los posts que hago lo puedes encontrar en este repositorio.

Joel H. Gomez Paredes

Written by

Google Developer Expert in Web Technologies And Google Maps Platform & A Reverse Developer & Fullsnack JS Developer

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade