Buscar..
Introducción
Las funciones del generador (definidas por la palabra clave function*
) se ejecutan como corrutinas, generando una serie de valores a medida que se solicitan a través de un iterador.
Sintaxis
- función * nombre (parámetros) {valor de rendimiento; valor de retorno}
- generador = nombre (argumentos)
- {value, done} = generator.next (value)
- {valor, hecho} = generador.retorno (valor)
- generador.throw (error)
Observaciones
Las funciones del generador son una característica introducida como parte de la especificación ES 2015 y no están disponibles en todos los navegadores. También son totalmente compatibles con Node.js a partir de la v6.0
. Para obtener una lista detallada de compatibilidad del navegador, consulte la Documentación de MDN y, para Nodo, visite el sitio web node.green .
Funciones del generador
Se crea una function*
generador con una declaración de function*
. Cuando se le llama, su cuerpo no se ejecuta inmediatamente. En su lugar, devuelve un objeto generador , que se puede usar para "pasar por" la ejecución de la función.
Una expresión de yield
dentro del cuerpo de la función define un punto en el que la ejecución se puede suspender y reanudar.
function* nums() {
console.log('starting'); // A
yield 1; // B
console.log('yielded 1'); // C
yield 2; // D
console.log('yielded 2'); // E
yield 3; // F
console.log('yielded 3'); // G
}
var generator = nums(); // Returns the iterator. No code in nums is executed
generator.next(); // Executes lines A,B returning { value: 1, done: false }
// console: "starting"
generator.next(); // Executes lines C,D returning { value: 2, done: false }
// console: "yielded 1"
generator.next(); // Executes lines E,F returning { value: 3, done: false }
// console: "yielded 2"
generator.next(); // Executes line G returning { value: undefined, done: true }
// console: "yielded 3"
Salida de iteración temprana
generator = nums();
generator.next(); // Executes lines A,B returning { value: 1, done: false }
generator.next(); // Executes lines C,D returning { value: 2, done: false }
generator.return(3); // no code is executed returns { value: 3, done: true }
// any further calls will return done = true
generator.next(); // no code executed returns { value: undefined, done: true }
Lanzar un error a la función del generador.
function* nums() {
try {
yield 1; // A
yield 2; // B
yield 3; // C
} catch (e) {
console.log(e.message); // D
}
}
var generator = nums();
generator.next(); // Executes line A returning { value: 1, done: false }
generator.next(); // Executes line B returning { value: 2, done: false }
generator.throw(new Error("Error!!")); // Executes line D returning { value: undefined, done: true}
// console: "Error!!"
generator.next(); // no code executed. returns { value: undefined, done: true }
Iteración
Un generador es iterable . Puede enlazarse con una sentencia for...of
, y usarse en otras construcciones que dependen del protocolo de iteración.
function* range(n) {
for (let i = 0; i < n; ++i) {
yield i;
}
}
// looping
for (let n of range(10)) {
// n takes on the values 0, 1, ... 9
}
// spread operator
let nums = [...range(3)]; // [0, 1, 2]
let max = Math.max(...range(100)); // 99
Aquí hay otro ejemplo de uso del generador para personalizar objetos iterables en ES6. Aquí función de function *
generador anónimo function *
utilizada.
let user = {
name: "sam", totalReplies: 17, isBlocked: false
};
user[Symbol.iterator] = function *(){
let properties = Object.keys(this);
let count = 0;
let isDone = false;
for(let p of properties){
yield this[p];
}
};
for(let p of user){
console.log( p );
}
Envío de valores al generador
Es posible enviar un valor al generador pasándolo al método next()
.
function* summer() {
let sum = 0, value;
while (true) {
// receive sent value
value = yield;
if (value === null) break;
// aggregate values
sum += value;
}
return sum;
}
let generator = summer();
// proceed until the first "yield" expression, ignoring the "value" argument
generator.next();
// from this point on, the generator aggregates values until we send "null"
generator.next(1);
generator.next(10);
generator.next(100);
// close the generator and collect the result
let sum = generator.next(null).value; // 111
Delegando a otro generador
Desde dentro de una función de generador, el control puede delegarse a otra función de generador usando el yield*
.
function* g1() {
yield 2;
yield 3;
yield 4;
}
function* g2() {
yield 1;
yield* g1();
yield 5;
}
var it = g2();
console.log(it.next()); // 1
console.log(it.next()); // 2
console.log(it.next()); // 3
console.log(it.next()); // 4
console.log(it.next()); // 5
console.log(it.next()); // undefined
Interfaz Iterator-Observer
Un generador es una combinación de dos cosas: un Iterator
y un Observer
.
Iterador
Un iterador es algo cuando invocado devuelve un iterable
. Un iterable
es algo que puedes iterar. Desde ES6 / ES2015 en adelante, todas las colecciones (Array, Map, Set, WeakMap, WeakSet) cumplen con el contrato de Iterable.
Un generador (iterador) es un productor. En iteración, el consumidor
PULL
es el valor del productor.
Ejemplo:
function *gen() { yield 5; yield 6; }
let a = gen();
Siempre que llame a.next()
, básicamente estás pull
valor del iterador -ing y pause
la ejecución en el yield
. La próxima vez que llame a a.next()
, la ejecución se reanudará desde el estado previamente pausado.
Observador
Un generador también es un observador mediante el cual puede enviar algunos valores de vuelta al generador.
function *gen() {
document.write('<br>observer:', yield 1);
}
var a = gen();
var i = a.next();
while(!i.done) {
document.write('<br>iterator:', i.value);
i = a.next(100);
}
Aquí puede ver que el yield 1
se usa como una expresión que se evalúa a algún valor. El valor que evalúa es el valor enviado como argumento a la a.next
función a.next
.
Entonces, por primera vez, i.value
será el primer valor producido ( 1
), y al continuar la iteración al siguiente estado, enviamos un valor al generador usando a.next(100)
.
Haciendo asíncrono con generadores.
Los generadores se usan ampliamente con la función spawn
(de taskJS o co), donde la función toma un generador y nos permite escribir código asíncrono de forma síncrona. Esto NO significa que el código asíncrono se convierta en código de sincronización / se ejecute de forma síncrona. Esto significa que podemos escribir código que parezca sync
pero internamente aún es async
.
La sincronización es BLOQUEO; Async está esperando. Escribir código que bloquee es fácil. Cuando PULLing, el valor aparece en la posición de asignación. Al presionar PUSH, el valor aparece en la posición de argumento de la devolución de llamada.
Cuando usas iteradores, PULL
el valor del productor. Cuando utiliza devoluciones de llamada, el productor PUSH
es el valor a la posición de argumento de la devolución de llamada.
var i = a.next() // PULL
dosomething(..., v => {...}) // PUSH
Aquí, a.next()
el valor de a.next()
y en el segundo, v => {...}
es la devolución de llamada y un valor es PUSH
ed en la posición de argumento v
de la función de devolución de llamada.
Usando este mecanismo pull-push, podemos escribir una programación asíncrona como esta,
let delay = t => new Promise(r => setTimeout(r, t));
spawn(function*() {
// wait for 100 ms and send 1
let x = yield delay(100).then(() => 1);
console.log(x); // 1
// wait for 100 ms and send 2
let y = yield delay(100).then(() => 2);
console.log(y); // 2
});
Entonces, mirando el código anterior, estamos escribiendo un código asíncrono que parece estar blocking
(las declaraciones de rendimiento esperan 100 ms y luego continúan la ejecución), pero en realidad están waiting
. La propiedad de pause
y resume
del generador nos permite hacer este increíble truco.
Como funciona ?
La función spawn utiliza la yield promise
para PULSAR el estado de promesa del generador, espera hasta que se resuelva la promesa y PUSA el valor resuelto al generador para que pueda consumirlo.
Úsalo ahora
Por lo tanto, con los generadores y la función spawn, puede limpiar todo su código asíncrono en NodeJS para que parezca que está sincronizado. Esto hará que la depuración sea fácil. También el código se verá limpio.
Esta característica llegará a las futuras versiones de JavaScript, ya que async...await
. Pero puede usarlos hoy en ES2015 / ES6 usando la función spawn definida en las bibliotecas - taskjs, co o bluebird
Flujo asíncrono con generadores.
Los generadores son funciones que pueden pausar y luego reanudar la ejecución. Esto permite emular funciones asíncronas utilizando bibliotecas externas, principalmente qo co. Básicamente, permite escribir funciones que esperan resultados asíncronos para continuar:
function someAsyncResult() {
return Promise.resolve('newValue')
}
q.spawn(function * () {
var result = yield someAsyncResult()
console.log(result) // 'newValue'
})
Esto permite escribir código asíncrono como si fuera síncrono. Por otra parte, tratar de atrapar el trabajo sobre varios bloques asíncronos. Si la promesa es rechazada, el error es atrapado por la siguiente captura:
function asyncError() {
return new Promise(function (resolve, reject) {
setTimeout(function () {
reject(new Error('Something went wrong'))
}, 100)
})
}
q.spawn(function * () {
try {
var result = yield asyncError()
} catch (e) {
console.error(e) // Something went wrong
}
})
Usar co funcionaría exactamente igual pero con co(function * (){...})
lugar de q.spawn