Generators

Los generators no pueden ser utilizados en es5 deben ser usados en es6 o superior.

Los generadores son funciones de las que se puede salir y volver a entrar. Su contexto (asociación de variables) será conservado entre las reentradas. Las reentradas son efectuadas gracias a la palabra reservada yield

Los generadores son funciones que debuelven un generator object, hay dos grandes motivaciones para utilizar este tipo de funciones, una de ellas es que los generators siguen la interfaz iterator permitiendo así utilizar los siguientes métodos next, return y throw. Y la otra razón la veremos un poco más adelante.

function* infiniteSequence() {
    var i = 0;
    while(true) {
        yield i++;
    }
}

var iterator = infiniteSequence();
while (true) {
    console.log(iterator.next()); // { value: xxxx, done: false } para siempre
}

Como se puede apreciar este tipo de funciones devuelve junto con el resultado el estado de la iteración, si la iteración termina bien devuelve false sino true. Aunque eso no significa que la función parará cuando se llegue al final de la iteración de un array u objeto. por ejemplo:

function* idMaker() {
    let index = 0;
    while (index < 3)
        yield index++;
}
let gen = idMaker();
console.log(gen.next()); // { value: 0, done: false }
console.log(gen.next()); // { value: 1, done: false }
console.log(gen.next()); // { value: 2, done: false }
console.log(gen.next()); // { value: undefined, done: true }
// Accede a la variable por que se le ha dicho apesar de que el 'yield' no ha sido efectuado.

Es importante ver que TypeScript nos permitiría compilar esto y acceder a una variable que no existe. Ya que el lenguaje hasta el momento de la ejecución (tiempo de ejecución) no nos permite saber el número de veces que utilizamos el next().

//------ main.ts --------
function* generator(){
    console.log('Execution started');
    yield 0;
    console.log('Execution resumed');
    yield 1;
    console.log('Execution resumed');
}

var iterator = generator();
console.log('Starting iteration'); // Esto se ejecutará antes que nada de dentro del método generator()
console.log(iterator.next()); // { value: 0, done: false }
console.log(iterator.next()); // { value: 1, done: false }
console.log(iterator.next()); // { value: undefined, done: true }
$ node main.js
Starting iteration
Execution started
{ value: 0, done: false }
Execution resumed
{ value: 1, done: false }
Execution resumed
{ value: undefined, done: true }

Como se puede apreciar en el ejemplo, lo primero que se ejecuta es el texto "Starting iteration", ya que no es hasta que ejecutemos el primer next(), que empezaremos a ejecutar el método generator(), dentro de esta función se pasa al método yield (si es encontrada) en cuyo caso se resume el estado del método, y se espera hasta el siguiente next() en el cual ejecutará el método desde el punto en el que se quedo en el estado anterior.

Este tipo de funciones también permite la inyección de variables como por ejemplo:

// ------- ejemplo.js ----------
function* generator() {
    var bar = yield 'Console log';
    console.log(bar); // 'Un testo inyectado' -> asignado por nextThing = iterator.next('bar')
    yield 1;
}.
const iterator = generator();
// Start execution till we get first yield value
const foo = iterator.next();
console.log(foo.value); // Console log
// Resume execution injecting bar
const nextThing = iterator.next('Un texto inyectado'); // Aqui se le asigna el value al foo.value
console.log(nextThing);
Console log
Un texto inyectado
{ value: 1, done: false }

Otro ejemplo de argumentos sería el siguiente:

function* logGenerator() {
  console.log(yield);
  console.log(yield);
  console.log(yield);
}

var gen = logGenerator();

// the first call of next executes from the start of the function
// until the first yield statement
gen.next(); 
gen.next('pretzel'); // pretzel
gen.next('california'); // california
gen.next('mayonnaise'); // mayonnaise

El siguiente ejemplo es como se tratarían las excepciones:

// --------- test.ts ------
function* generator() {
    try {
        yield 'foo';
        throw Error("Test");
    }
    catch(err) {
        console.log(err.message); // bar!
    }
}

var iterator = generator();
// Start execution till we get first yield value
var foo = iterator.next();
console.log(foo.value);
// como está comentado la excepción no se ejuta ya que no hay un 'next()'
//var foo = iterator.next();

Output

foo

Ahora veamos el output sin comentar esa línea:

// --------- test.ts ------
function* generator() {
    try {
        yield 'foo';
        throw Error("Test");
    }
    catch(err) {
        console.log(err.message); // bar!
    }
}

var iterator = generator();
// Start execution till we get first yield value
var foo = iterator.next();
console.log(foo.value);
var foo = iterator.next();

Output

foo
Test

La otra gran razón por la cual es tan importante la utilización de este tipo de métodos es porque con este tipo de métodos podemos mejorar el sistema de promesas y las peticiones asíncronas. como por ejemplo:

function getFirstName() {
    setTimeout(function(){
        gen.next('alex')
    }, 1000);
}

function getSecondName() {
    setTimeout(function(){
        gen.next('perry')
    }, 1000);
}


function *sayHello() {
    var a = yield getFirstName();
    var b = yield getSecondName();
    console.log(a, b); //alex perry
}

var gen = sayHello();

gen.next();

results matching ""

    No results matching ""