Skip to content

Instantly share code, notes, and snippets.

@brauliodiez
Last active January 23, 2019 18:16
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save brauliodiez/11201ccc6113ae37034a3693e0e757ac to your computer and use it in GitHub Desktop.
Save brauliodiez/11201ccc6113ae37034a3693e0e757ac to your computer and use it in GitHub Desktop.
Notas del training de generadores (Español)

Conceptos básicos

En javascript cuando llamamos a una función esta se ejecuta de inicio a final.

function logger() {
console.log('Start');
console.log('End');
}

logger();

Ejemplo codepen

Las funciones generadores de ES6 permite pausar la ejecución de una función y reanudarla (añadimos un “*” a nuestra función).

- function* logger() {
+ function* logger() {
console.log('Start');
console.log('End');
}

logger();

https://codepen.io/Lemoncode/pen/gQdzON?editors=0012

Si ejecutamos esto, no tenemos resultados, esto es porque cuando llamamos a un generador devuelve una instancia de si mismo.

Renombramos la función a createLogger y llamando a next la podemos ejecutar.

- function* logger() {
+ function* createLogger() {
console.log('Start');
console.log('End');
}

- logger();
+ const logger  = CreateLogger();
+ logger.next();

https://codepen.io/Lemoncode/pen/WYgJjG?editors=0001

Ahora si ejecutamos vemos como la función de se ejecuta de principio a fin.

¿Cómo podemos pausar la ejecución de nuestra función generadora? Usando el comando yield

function* CreateLogger() {
  console.log('Start');
+ yield;
  console.log('End');
}

const logger  = CreateLogger();
logger.next();

Si ejecutamos veremos que sólo vemos start en nuestra consola.

https://codepen.io/Lemoncode/pen/pQOVrw?editors=0011

Para poder llegar hasta el final de la función tendríamos que volver a ejecutar logger.next

function* CreateLogger() {
  console.log('Start');
  yield;
  console.log('End');
}

const logger  = CreateLogger();
logger.next();
+ logger.next();

Podemos añadir tantos yields como queramos (tendremos que llamar tantos next como yields tengamos).

Cuando tenemos un generador y una instancia del generador podemos enviar mensajes desde el generador a la instancia y viceversa.

Vamos a probar a enviar un mensaje desde el generador a la instancia activa:

Updated function name !*

function* createHello() {
  yield 'first'
}

const hello = createHello();
console.log(hello.next());

Cuando ejecutamos se muestra por consola la siguiente salida:

{value: 'first', done: false}

¿Pero se ejecuta la función hasta al final? Vamos a verlo:

function* createHello() {  
  console.log('before yield');
  yield 'first';  
  console.log('after yield');
}

const hello = createHello();
console.log(hello.next());

En valor tenemos el valor que yield devolvío, y la salida done nos indica que la función generador no ha terminado aún.

Para terminar de ejecutar nuestra llamada al generador basta con hacer otra llamada a next

function* createHello() {
  yield 'first'
}

const hello = createHello();
console.log(hello.next);
+ console.log(hello.next);

Está segunda llamada nos muestra la siguiente salida por consola:

{value: undefined, done: true}

Es decir no hay valor devuelto por yield (hemos terminado de ejecutar la función, y done está a true, indicandos que nuestra generador ha terminado con la ejecución.

También podemos pasar información desde nuestra instancia del generador al generador, veamos como:

function* createHello() {
  const word = yield;
  console.log(word);
}


const hello = createHello();
console.log(hello.next());
console.log(hello.next('Max'));

Veamos lo que obtenemos por consola:

{value: undefined, done: false}
Max
{value: undefined, done: true}

Control de errores

Vamos a ver como controlar errores desde un generador.

Cuando invocamos a un generador parece que tenemos un código síncrono, y en realidad estamos ejecutando un flujo asínncono, es decir entre un next y otro next pueden ocurrir mil cosas.

function* createHello() {
  const word = yield;
  console.log(word);
}


const hello = createHello();
console.log(hello.next());
// ... Many operations (async) before resuming our generation
console.log(hello.next('Max'));

Si algo va mal en algo en el código que estamos haciendo, ¿ Cómo podemos informar al generador de que ha ocurrido un error? Podemos lanzar un throw desde fuera, y en el generador capurar la excepción con un try catch

function* createHello() {
+  try {
const word = yield;
  console.log(word);
+ }
+ catch(err) {
+   console.log(`Error: ${err}`);
+ }
}


const hello = createHello();
console.log(hello.next());
// ... Many operations (async) before resuming our generation
+ hello.throw('Fatal error...');
console.log(hello.next('Max'));

https://codepen.io/Lemoncode/pen/gQdKyv?editors=0012

Iterando en un generador

  • Vamos a ver ahora como iterar en un generador, para ello es6 nos provee del bucle for of que nos permite iterar por un generador hasta que termina done, un ejemplo de esto:
function * createCounter() {
  yield "Hello from step 1";
  yield "Hello from step 2";
  yield "Hello from step 3";
  yield "Hello from step 4";
}

const counter = createCounter();
for(let message of counter) {
 console.log(message);
}

Esto nos genera la siguiente salida por consola:

Hello from step 1
Hello from step 2
Hello from step 3
Hello from step 4

https://codepen.io/Lemoncode/pen/vQzaBe?editors=0010

¿ Funcionaría map _reduce? No, tenemos que hacernos el nuestro: https://stackoverflow.com/questions/45983883/mapping-a-function-on-a-generator-in-javascript un ejemplo: https://dev.to/nestedsoftware/lazy-evaluation-in-javascript-with-generators-map-filter-and-reduce--36h5

  • También podemos tener generadores anidados, por ejemplo si tenemos:
function* create3To4Counter() {
  yield 3;
  yield 4;
}

function* createCounter() {
  yield 1;
  yield 2;
  yield 5;  
}

for(let i of createCounter()) {
  console.log(i);
}

Y queremos interacalar la ejecuión del primero generador dentro del segundo, entre le paso 2 y el 5 sólo tenemos que introducir la siguiente linea de código:

function* create3To4Counter() {
  yield 3;
  yield 4;
}

function* createCounter() {
  yield 1;
  yield 2;
+ yield* create3To4Counter();
  yield 5;  
}

for(let i of createCounter()) {
  console.log(i);
}

Esto genera la siguiente salida por la consola del navegador:

1
2
3
4
5

Otro tema interesante es que podemos hacer un return de un valor en el primer generador y que lo coja el segundo.

function* create3To4Counter() {
  yield 3;
-  yield 4;
+  return 4;
}

function* createCounter() {
  yield 1;
  yield 2;
  const four  = yield* create3To4Counter();
  console.log(four);
  yield 5;  
}

for(let i of createCounter()) {
  console.log(i);
}

Esto nos daría la misma salida por consola que el paso anterior.

https://codepen.io/Lemoncode/pen/zMJLoB?editors=0010

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment