¿Cuál es la palabra clave de rendimiento en JavaScript?


238

Escuché sobre una palabra clave de "rendimiento" en JavaScript, pero encontré muy poca documentación al respecto. ¿Alguien puede explicarme (o recomendar un sitio que explique) su uso y para qué se utiliza?


Probablemente quiere decir 'Rendimiento' bytes.com/topic/python/answers/685510-yield-keyword-usage
ant

44
se explica en MDN , pero creo que esto solo funciona para firefox, ¿verdad? ¿Qué tan portátil es? ¿Alguna forma de hacerlo en Chrome o node.js? PD: lo siento, es Javascript v1.7 + , así que esa es la propiedad a tener en cuenta al buscar soporte.
Trylks

1
@ Trylks: los generadores están disponibles en Node desde v0.11.2
Janus Troelsen

@JanusTroelsen sin embargo, solo detrás de una bandera. Son compatibles de forma nativa en ioJS
Dan Pantry

Respuestas:


86

La documentación de MDN es bastante buena, en mi opinión.

La función que contiene la palabra clave de rendimiento es un generador. Cuando lo llama, sus parámetros formales están vinculados a argumentos reales, pero su cuerpo no se evalúa realmente. En cambio, se devuelve un generador-iterador. Cada llamada al método next () del generador-iterador realiza otra pasada a través del algoritmo iterativo. El valor de cada paso es el valor especificado por la palabra clave de rendimiento. Piense en el rendimiento como la versión de retorno generador-iterador, que indica el límite entre cada iteración del algoritmo. Cada vez que llama a next (), el código del generador se reanuda a partir de la declaración que sigue al rendimiento.


2
@NicolasBarbulesco hay un ejemplo muy obvio si hace clic en la documentación de MDN.
Matt Ball

@MattBall: una función como javascript para PI como esta sería suficiente de la siguiente manera: function * PI {PI = ((Math.SQRT8;) / 9801;); } - ¿o ya hay una función implementada en javascript para este cálculo de PI?
dschinn1001

44
¿Cuál es el punto de citar MDN aquí? Creo que todos pueden leer eso en MDN. Visite davidwalsh.name/promises para obtener más información sobre ellos.
Ejaz Karim

20
¿Cómo obtuvo esto ~ 80 votos positivos cuando (a) es una copia de la "documentación muy pobre" como la llama el interrogador y (b) no dice nada útil? Mucho mejor respuestas a continuación.
www-0av-Com

44
Si alguien solicita una explicación, simplemente copiar y pegar una documentación es totalmente inútil. Preguntar significa que ya buscó en documentos pero no los entendió.
Diego

205

Respuesta tardía, probablemente todo el mundo lo sepa yieldahora, pero ha aparecido una mejor documentación.

Adaptando un ejemplo de "Javascript's Future: Generators" de James Long para el estándar oficial de Harmony:

function * foo(x) {
    while (true) {
        x = x * 2;
        yield x;
    }
}

"Cuando llamas a foo, obtienes un objeto Generator que tiene un siguiente método".

var g = foo(2);
g.next(); // -> 4
g.next(); // -> 8
g.next(); // -> 16

Entonces yieldes algo así como return: recuperas algo. return xdevuelve el valor de x, pero yield xdevuelve una función, que le brinda un método para iterar hacia el siguiente valor. Es útil si tiene un procedimiento potencialmente intensivo en memoria que puede querer interrumpir durante la iteración.


13
Útil, pero supongo que estás function* foo(x){allí
Rana Deep

99
@RanaDeep: la sintaxis de la función se extiende para agregar un token opcional * . Si lo necesita o no, depende del tipo de futuro que regrese. El detalle es largo: GvR lo explica para la implementación de Python , sobre el cual se modela la implementación de Javascript. El uso function *siempre será correcto, aunque en algunos casos un poco más sobrecargado que functioncon yield.
obispo el

1
@ Ajedi32 Sí, tienes razón. Harmony estandarizó la correlación entre function *y yield, y agregó el error citado ("Se genera un error temprano si se produce una expresión de rendimiento o rendimiento * en una función no generadora"). Pero, la implementación original de Javascript 1.7 en Firefox no requería el* . Respuesta actualizada en consecuencia. ¡Gracias!
obispo

3
@MuhammadUmer Js finalmente se convierte en un lenguaje que realmente puedes usar. Se llama evolución.
Lukas Liesis

1
ejemplo es útil, pero ... ¿qué es una función *?
Diego

66

Es realmente simple, así es como funciona

  • yieldLa palabra clave simplemente ayuda a pausar y reanudar una función en cualquier momento de forma asincrónica .
  • Además, ayuda a devolver el valor de una función generadora .

Tome esta sencilla función de generador :

function* process() {
    console.log('Start process 1');
    console.log('Pause process2 until call next()');

    yield;

    console.log('Resumed process2');
    console.log('Pause process3 until call next()');

    let parms = yield {age: 12};
    console.log("Passed by final process next(90): " + parms);

    console.log('Resumed process3');
    console.log('End of the process function');
}

let _process = process ();

Hasta que llame la _process.next () se planteo ejecutar las 2 primeras líneas de código, a continuación, la primera cosecha será pausar la función. Para reanudar la función hasta el siguiente punto de pausa ( palabra clave de rendimiento ), debe llamar a _process.next () .

Puede pensar que los rendimientos múltiples son los puntos de interrupción en un depurador de JavaScript dentro de una sola función. Hasta que le indique que navegue por el siguiente punto de interrupción, no ejecutará el bloque de código. ( Nota : sin bloquear toda la aplicación)

Pero mientras que los comportamientos de rendimiento Realiza esta pausa y reanudar puede devolver algunos resultados así {value: any, done: boolean} de acuerdo a la función anterior no hemos emitir cualquier valor. Si exploramos la salida anterior, mostrará lo mismo { value: undefined, done: false } con un valor indefinido .

Vamos a profundizar en la palabra clave de rendimiento. Opcionalmente, puede agregar expresión y establecer asignar un valor opcional predeterminado . (Sintaxis oficial del documento)

[rv] = yield [expression];

expresión : Valor a devolver de la función generadora

yield any;
yield {age: 12};

rv : devuelve el valor opcional que pasó al método next () del generador

Simplemente puede pasar parámetros a la función process () con este mecanismo, para ejecutar diferentes partes de rendimiento.

let val = yield 99; 

_process.next(10);
now the val will be 10 

Pruebalo ahora

Usos

  • Evaluación perezosa
  • Secuencias infinitas
  • Control asincrónico fluye

Referencias


54

Simplificando / elaborando la respuesta de Nick Sotiros (que creo que es increíble), creo que es mejor describir cómo comenzaría a codificar yield.

En mi opinión, la mayor ventaja de usar yieldes que eliminará todos los problemas de devolución de llamada anidados que vemos en el código. Al principio es difícil ver cómo, por eso decidí escribir esta respuesta (¡para mí y, con suerte, para los demás!)

La forma en que lo hace es introduciendo la idea de una co-rutina, que es una función que puede detener / pausar voluntariamente hasta que obtenga lo que necesita. En javascript, esto se denota por function*. Solo las function*funciones pueden usar yield.

Aquí hay algunos javascript típicos:

loadFromDB('query', function (err, result) {
  // Do something with the result or handle the error
})

Esto es torpe porque ahora todo su código (que obviamente necesita esperar esta loadFromDBllamada) debe estar dentro de esta devolución de llamada fea. Esto es malo por algunas razones ...

  • Todo su código está sangrado un nivel en
  • Tienes este final })que debes seguir en todas partes
  • Toda esta function (err, result)jerga extra
  • No está claro que esté haciendo esto para asignar un valor a result

Por otro lado, con yieldtodo esto se puede hacer en una línea con la ayuda del agradable marco de co-rutina.

function* main() {
  var result = yield loadFromDB('query')
}

Y ahora, su función principal cederá cuando sea necesario cuando necesite esperar a que se carguen las variables y las cosas. Pero ahora, para ejecutar esto, debe llamar a una función normal (no corutina). Un marco de co-rutina simple puede solucionar este problema, de modo que todo lo que tiene que hacer es ejecutar esto:

start(main())

Y el inicio está definido (de la respuesta de Nick Sotiro)

function start(routine, data) {
    result = routine.next(data);
    if(!result.done) {
        result.value(function(err, data) {
            if(err) routine.throw(err); // continue next iteration of routine with an exception
            else start(routine, data);  // continue next iteration of routine normally
        });
    }
}

Y ahora, puede tener un código hermoso que es mucho más legible, fácil de eliminar y no necesita manipular sangrías, funciones, etc.

Una observación interesante es que, en este ejemplo, en yieldrealidad es solo una palabra clave que puede poner antes de una función con una devolución de llamada.

function* main() {
  console.log(yield function(cb) { cb(null, "Hello World") })
}

Imprimiría "Hola Mundo". Por lo tanto, puede convertir cualquier función de devolución de llamada en yieldsimplemente creando la misma firma de función (sin el cb) y regresando function (cb) {}, de esta manera:

function yieldAsyncFunc(arg1, arg2) {
  return function (cb) {
    realAsyncFunc(arg1, arg2, cb)
  }
}

Espero que con este conocimiento pueda escribir un código más limpio y legible que sea fácil de eliminar .


a function*es solo una función regular sin rendimiento?
Abdul

Creo que quiere decir que function *es una función que contiene rendimiento. Es una función especial llamada generador.
Leander

77
Para las personas que ya usan en yieldtodas partes, estoy seguro de que esto tiene más sentido que las devoluciones de llamada, pero no veo cómo esto es más legible que las devoluciones de llamada.
palswim

ese artículo es difícil de entender
Martian2049

18

Para dar una respuesta completa: yieldestá funcionando de manera similar return, pero en un generador.

En cuanto al ejemplo comúnmente dado, esto funciona de la siguiente manera:

function *squareGen(x) {
    var i;
    for (i = 0; i < x; i++) {
        yield i*i;
    }
}

var gen = squareGen(3);

console.log(gen.next().value); // prints 0
console.log(gen.next().value); // prints 1
console.log(gen.next().value); // prints 4

Pero también hay un segundo propósito de la palabra clave de rendimiento. Se puede usar para enviar valores al generador.

Para aclarar, un pequeño ejemplo:

function *sendStuff() {
    y = yield (0);
    yield y*y;
}

var gen = sendStuff();

console.log(gen.next().value); // prints 0
console.log(gen.next(2).value); // prints 4

Esto funciona, ya que 2se le asigna el valor y, enviándolo al generador, después de que se detuvo en el primer rendimiento (que regresó 0).

Esto nos permite hacer algunas cosas realmente funky. (buscar corutina)



6

yield También se puede utilizar para eliminar el infierno de devolución de llamada, con un marco de trabajo de rutina.

function start(routine, data) {
    result = routine.next(data);
    if(!result.done) {
        result.value(function(err, data) {
            if(err) routine.throw(err); // continue next iteration of routine with an exception
            else start(routine, data);  // continue next iteration of routine normally
        });
    }
}

// with nodejs as 'node --harmony'
fs = require('fs');
function read(path) {
    return function(callback) { fs.readFile(path, {encoding:'utf8'}, callback); };
}

function* routine() {
    text = yield read('/path/to/some/file.txt');
    console.log(text);
}

// with mdn javascript 1.7
http.get = function(url) {
    return function(callback) { 
        // make xhr request object, 
        // use callback(null, resonseText) on status 200,
        // or callback(responseText) on status 500
    };
};

function* routine() {
    text = yield http.get('/path/to/some/file.txt');
    console.log(text);
}

// invoked as.., on both mdn and nodejs

start(routine());

4

Generador de secuencia de Fibonacci usando la palabra clave de rendimiento.

function* fibbonaci(){
    var a = -1, b = 1, c;
    while(1){
        c = a + b;
        a = b;
        b = c;
        yield c;
    }   
}

var fibonacciGenerator = fibbonaci();
fibonacciGenerator.next().value; // 0 
fibonacciGenerator.next().value; // 1
fibonacciGenerator.next().value; // 1
fibonacciGenerator.next().value; // 2 

4

Yeild la palabra clave en la función javaScript lo hace generador,

¿Qué es el generador en JavaScript?

Un generador es una función que produce una secuencia de resultados en lugar de un solo valor, es decir, genera una serie de valores

Lo que significa que los generadores nos ayudan a trabajar de forma asíncrona con los iteradores de ayuda. ¿Ahora qué son los iteradores de pirateo? ¿De Verdad?

Los iteradores son medios a través de los cuales podemos acceder a los elementos uno a la vez

¿desde dónde el iterador nos ayuda a acceder al elemento uno a la vez? nos ayuda a acceder a los elementos a través de las funciones del generador,

las funciones generadoras son aquellas en las que usamos yeildpalabras clave, la palabra clave de rendimiento nos ayuda a pausar y reanudar la ejecución de la función

Aquí hay un ejemplo rápido

function *getMeDrink() {

    let question1 = yield 'soda or beer' // execution will pause here because of yield

 if (question1 == 'soda') {

            return 'here you get your soda'

    }

    if (question1 == 'beer') {

        let question2 = yield 'Whats your age' // execution will pause here because of yield

        if (question2 > 18) {

            return "ok you are eligible for it"

        } else {

            return 'Shhhh!!!!'

        }
    }
}


let _getMeDrink = getMeDrink() // initialize it

_getMeDrink.next().value  // "soda or beer"

_getMeDrink.next('beer').value  // "Whats your age"

_getMeDrink.next('20').value  // "ok you are eligible for it"

_getMeDrink.next().value // undefined

déjame explicar brevemente lo que está pasando

notó que la ejecución se detiene en cada yeildpalabra clave y podemos acceder primero yieldcon la ayuda del iterador.next()

esto itera a todas las yieldpalabras clave una a la vez y luego regresa indefinido cuando no yieldquedan más palabras clave en palabras simples, puede decir que la yieldpalabra clave es el punto de interrupción donde la función se detiene cada vez y solo se reanuda cuando se llama usando el iterador

para nuestro caso: _getMeDrink.next()este es un ejemplo de iterador que nos está ayudando a acceder a cada punto de interrupción en la función

Ejemplo de generadores: async/await

si ves la implementación de async/await verás generator functions & promisesse utilizan para hacer el async/awaittrabajo

por favor señale cualquier sugerencia es bienvenida


3

Dependencia entre llamadas async javascript.

Otro buen ejemplo de cómo se puede usar el rendimiento.

function request(url) {
  axios.get(url).then((reponse) => {
    it.next(response);
  })
}

function* main() {
  const result1 = yield request('http://some.api.com' );
  const result2 = yield request('http://some.otherapi?id=' + result1.id );
  console.log('Your response is: ' + result2.value);
}

var it = main();
it.next()


0

Antes de aprender sobre el rendimiento, necesita saber sobre los generadores. Los generadores se crean utilizando la function*sintaxis. Las funciones del generador no ejecutan código, sino que devuelve un tipo de iterador llamado generador. Cuando se da un valor usando el nextmétodo, la función de generador se sigue ejecutando hasta que se encuentra con una palabra clave de rendimiento. El uso yieldle devuelve un objeto que contiene dos valores, uno es valor y el otro está hecho (booleano). El valor puede ser una matriz, un objeto, etc.


0

Un simple ejemplo:

const strArr = ["red", "green", "blue", "black"];

const strGen = function*() {
    for(let str of strArr) {
        yield str;
    }
};

let gen = strGen();

for (let i = 0; i < 5; i++) {
    console.log(gen.next())
}

//prints: {value: "red", done: false} -> 5 times with different colors, if you try it again as below:

console.log(gen.next());

//prints: {value: undefined, done: true}

0

También estoy tratando de entender la palabra clave de rendimiento. Según mi comprensión actual, en el generador, la palabra clave de rendimiento funciona como un cambio de contexto de CPU. Cuando se ejecuta la declaración de rendimiento, se guardan todos los estados (por ejemplo, variables locales).

Además de esto, se devolverá un objeto de resultado directo a la persona que llama, como {valor: 0, hecho: falso}. La persona que llama puede usar este objeto de resultado para decidir si 'reactiva' el generador nuevamente llamando a next () (llamar a next () es repetir la ejecución).

Otra cosa importante es que puede establecer un valor en una variable local. La persona que llama 'next ()' puede pasar este valor al 'despertar' el generador. por ejemplo, it.next ('valueToPass'), como este: "resultValue = yield slowQuery (1);" Al igual que cuando se activa una próxima ejecución, la persona que llama puede inyectar algún resultado en ejecución a la ejecución (inyectándolo en la variable local). Por lo tanto, para esta ejecución, hay dos tipos de estado:

  1. El contexto que se guardó en la última ejecución.

  2. Los valores inyectados por el disparador de esta ejecución.

Entonces, con esta característica, el generador puede ordenar múltiples operaciones asíncronas. El resultado de la primera consulta asincrónica se pasará a la segunda configurando la variable local (resultValue en el ejemplo anterior). La segunda consulta asíncrona solo puede ser activada por la respuesta de la primera consulta asíncrona. Luego, la segunda consulta asíncrona puede verificar el valor de la variable local para decidir los próximos pasos porque la variable local es un valor inyectado de la respuesta de la primera consulta.

Las dificultades de las consultas asíncronas son:

  1. devolución de llamada infierno

  2. perder de contexto a menos que los pase como parámetros en la devolución de llamada.

el rendimiento y el generador pueden ayudar en ambos.

Sin rendimiento y generador, para ordenar múltiples consultas asíncronas requiere devolución de llamada anidada con parámetros como contexto que no es fácil de leer y mantener.

A continuación se muestra un ejemplo de consultas asíncronas encadenadas que se ejecuta con nodejs:

const axios = require('axios');

function slowQuery(url) {        
    axios.get(url)
    .then(function (response) {
            it.next(1);
    })
    .catch(function (error) {
            it.next(0);
    })
}

function* myGen(i=0) {
    let queryResult = 0;

    console.log("query1", queryResult);
    queryResult = yield slowQuery('https://google.com');


    if(queryResult == 1) {
        console.log("query2", queryResult);
        //change it to the correct url and run again.
        queryResult = yield slowQuery('https://1111111111google.com');
    }

    if(queryResult == 1) {
        console.log("query3", queryResult);
        queryResult =  yield slowQuery('https://google.com');
    } else {
        console.log("query4", queryResult);
        queryResult = yield slowQuery('https://google.com');
    }
}

console.log("+++++++++++start+++++++++++");
let it = myGen();
let result = it.next();
console.log("+++++++++++end+++++++++++");

A continuación se muestra el resultado de ejecución:

+++++++++++ start +++++++++++

consulta1 0

+++++++++++ end +++++++++++

query2 1

consulta4 0

El siguiente patrón de estado puede hacer lo mismo para el ejemplo anterior:

const axios = require('axios');

function slowQuery(url) {
    axios.get(url)
        .then(function (response) {
            sm.next(1);
        })
        .catch(function (error) {
            sm.next(0);
        })
}

class StateMachine {
        constructor () {
            this.handler = handlerA;
            this.next = (result = 1) => this.handler(this, result);
        }
}

const handlerA = (sm, result) => {
                                    const queryResult = result; //similar with generator injection
                                    console.log("query1", queryResult);
                                    slowQuery('https://google.com');
                                    sm.handler = handlerB; //similar with yield;
                                };

const handlerB = (sm, result) => {
                                    const queryResult = result; //similar with generator injection
                                    if(queryResult == 1) {
                                        console.log("query2", queryResult);
                                        slowQuery('https://1111111111google.com');
                                    }
                                    sm.handler = handlerC; //similar with yield;
                                };

const handlerC = (sm, result) => {
                                    const queryResult = result; //similar with generator injection;
                                    if (result == 1 ) {
                                        console.log("query3", queryResult);
                                        slowQuery('https://google.com');
                                    } else {
                                        console.log("query4", queryResult);
                                        slowQuery('https://google.com');
                                    }
                                    sm.handler = handlerEnd; //similar with yield;
                                };

const handlerEnd = (sm, result) => {};

console.log("+++++++++++start+++++++++++");
const sm = new StateMachine();
sm.next();
console.log("+++++++++++end+++++++++++");

El siguiente es el resultado de ejecución:

+++++++++++ start +++++++++++

consulta1 0

+++++++++++ end +++++++++++

query2 1

consulta4 0


0

no olvide la útil sintaxis 'x of generator' para recorrer el generador. No es necesario utilizar la función next () en absoluto.

function* square(x){
    for(i=0;i<100;i++){
        x = x * 2;
        yield x;        
    }   
}

var gen = square(2);
for(x of gen){
   console.log(x);
}
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.