¡Rendimiento! ¡Rendimiento! Cómo funcionan los generadores en JavaScript.

Si el título aún no da una pista, discutiremos los generadores en este artículo.

Antes de entrar en los generadores, repasemos algunos conceptos básicos sobre funciones.

  • En JavaScript, las funciones son un conjunto de declaraciones que realizan una tarea y devuelven algún valor que finaliza la función.
  • Si llama a una función, una y otra vez, ejecutará todas las declaraciones una y otra vez.
  • Una vez que salen del arco, las flechas no se pueden detener, solo acertan o fallan. De la misma manera que una función una vez llamada no se puede detener, se ejecutará, devolverá un valor, arrojará un error y luego se detendrá después de ejecutar todas las declaraciones.

Solo debemos tener en cuenta estos 3 puntos para entender los generadores.

Generadores

Un generador es un tipo especial de función que puede detener su ejecución a mitad de camino y luego comenzar desde el mismo punto después de un tiempo. Los generadores son una combinación de funciones e iteradores. Esta es una declaración un poco confusa, pero me aseguraré de que al final del artículo esta línea quede clara.

Para mayor claridad, considere jugar un juego y, de repente, mamá pide algo de trabajo. Pausas el juego, la ayudas y luego vuelves a jugar. Lo mismo ocurre con los generadores.

Un iterador es un objeto que define una secuencia y potencialmente un valor de retorno tras su terminación. - MDN.

Los iteradores en sí mismos son un tema enorme y no son el objetivo de este artículo.

Sintaxis básica

Los generadores se definen como una función con un asterisco (*) al lado de la función.

function* name(arguments) { statements}

name: el nombre de la función.

argumentos: argumentos para la función.

declaraciones: el cuerpo de la función.

Regreso

Una función puede devolver casi cualquier cosa, desde un valor, objeto u otra función en sí. Una función generadora devuelve un objeto especial llamado objeto generador ( no es del todo cierto ). El objeto se parece al fragmento de abajo

false

El objeto tiene dos propiedades value y done. El valor contiene el valor que se obtendrá. Done consiste en un booleano (verdadero | falso) que le dice al generador si .next () dará un valor o indefinido.

La declaración anterior será difícil de digerir. Cambiemos eso con un ejemplo.

function* generator(e) { yield e + 10; yield e + 25; yield e + 33;}var generate = generator(27);
console.log(generate.next().value); // 37console.log(generate.next().value); // 52console.log(generate.next().value); // 60console.log(generate.next().value); // undefined

Entendamos la mecánica del código anterior línea por línea.

líneas 1 a 5: las líneas 1 a 5 definen el generador que tiene el mismo nombre con un argumento e. Dentro del cuerpo de la función, contiene un montón de declaraciones con la palabra clave yield y después de eso se realiza alguna operación.

línea 6: La línea 6 asigna el generador a una variable llamada generate.

líneas 8-11: estas líneas llaman a un grupo deconsole.logcada una que llama al generador encadenado a unnext método que llama a lavaluepropiedad del objeto generador.

Siempre que se invoca una función generadora, a diferencia de las funciones normales, no inicia la ejecución de inmediato. En su lugar, se devuelve un iterador ( la razón real * es utilizada por un generador. Le dice a JS que se debe devolver un objeto iterador ). Cuando next()se llama al método del iterador, la ejecución del generador comienza y se ejecuta hasta que encuentra la primera yield declaración. En este punto de fluencia se devuelve el objeto generador, cuyas especificaciones ya se explican. Llamar a la next()función nuevamente reanudará la función del generador hasta que encuentre otra yield declaración y el ciclo regrese hasta que yields se agoten todos .

Después de este punto, si next se llama, devuelve el objeto generador con un valor indefinido.

Ahora intentemos generar otra función generadora del generador original y también una declaración de retorno.

Una returndeclaración en un generador hará que el generador finalice su ejecución como cualquier otra función. La done property del objeto generador se establecerá en true y la value devolución se establecerá en la value propiedad del objeto generador. Todos los demás yields volverán undefined.

Si se arroja un error, también se detendrá la ejecución del generador, produciendo un generador en sí.

Para yielding un generador, necesitamos especificar un * contra el yield para decirle a JS que se produce un generador. Los yield*delegados a otra función generadora - esa es la razón por la que podemos yield todos los valores de la generator2 función usando el generate.next()de la función generadora original. El primer valor yieldedes de la primera función del generador y los dos últimos yielded valores son generados por la función del generador pero yielded por el generador original.

Ventajas

Carga lenta

La carga diferida es esencialmente una evaluación de valor solo cuando es necesario. Como veremos en un ejemplo que viene, podemos hacerlo con generadores. Es posible que solo obtengamos los valores cuando sea necesario y no todos al mismo tiempo.

El siguiente ejemplo es de otro ejemplo de este artículo y genera infinitos números aleatorios. Aquí podemos ver que podemos llamar a tantos next()como queramos y no obtener todos los valores que está produciendo. Solo los necesarios.

function * randomize() { while (true) {let random = Math.floor(Math.random()*1000); yield random; }}
var random= randomize();
console.log(random.next().value)

Memoria eficiente

Como podemos deducir del ejemplo anterior, los generadores son extremadamente eficientes en memoria. Como queremos los valores solo de acuerdo con las necesidades, necesitamos muy menos almacenamiento para almacenar esos valores.

Trampas

Los generadores son extremadamente útiles pero también tienen sus propios inconvenientes.

  • Los generadores no proporcionan acceso aleatorio como matrices y otras estructuras de datos. Como los valores se obtienen uno por uno, no podemos acceder a elementos aleatorios.
  • Los generadores brindan acceso único. Los generadores no le permiten iterar los valores una y otra vez. Una vez que se agotan todos los valores, tenemos que crear una nueva instancia de Generator para iterar todos los valores nuevamente.

¿Por qué necesitamos generadores?

Los generadores proporcionan una amplia variedad de usos en JavaScript. Intentemos recrear algunos nosotros mismos.

Implementando iteradores

Un iterador es un objeto que permite a un programador atravesar un contenedor -Wikipedia

Imprimiremos todas las palabras presentes en una cadena usando iteradores. Las cadenas también son iteradores.

Iteradores

const string = 'abcde';const iterator = string[Symbol.iterator]();console.log(iterator.next().value)console.log(iterator.next().value)console.log(iterator.next().value)console.log(iterator.next().value)console.log(iterator.next().value)

Aquí es lo mismo usando generadores

function * iterator() {yield 'a';yield 'b';yield 'c';yield 'd';yield 'e';}for (let x of iterator()) {console.log(x);}

Comparando ambos métodos, es fácil ver que con la ayuda de generadores podemos hacerlo con menos desorden. Sé que no es un muy buen ejemplo, pero es suficiente para probar los siguientes puntos:

  • Sin implementación de next()
  • No [Symbol.iterator]() invocation
  • In some cases, we even need to set the object.done property return value to true/false using iterators.

Async-Await ~ Promises+Generators

You can read my previous article about Async/Await if you want to learn about them, and check out this for Promises.

Crudely, Async/Await is just an implementation of Generators used with Promises.

Async-Await

async function async-await(){let a=await(task1);console.log(a);
let b=await(task2);console.log(b);
let c=await(task3);console.log(c);
}

Promises+Generators

function * generator-promise(){let a=yield Promise1();console.log(a);let b=yield Promise1();console.log(b);let c=yield Promise1();console.log(c);
}

As we can see, both produce the same result and almost in a similar fashion too. It’s because the Async/Await mechanism is loosely based on a combination of generators and promise. There is a lot more to Async/Await than shown above, but just for showing the use of a generator, we can consider this.

Infinite Data Structure

The heading might be a little misleading, but it is true. We can create generators, with the use of a while loop that will never end and will always yield a value.

function * randomize() { while (true) {let random = Math.floor(Math.random()*1000); yield random; }}var random= randomize();while(true)console.log(random.next().value)

In the above snippet, we create an infinite generator, which will yield a random number on every next() invocation. It can be called as an infinite stream of random numbers. This is a very basic example.

Conclusion

There is yet a lot to be covered about generators, and this was just an introduction to the topic. Hope you learned something new and the article was easy to understand.

Follow me and applaud!