Qual è la parola chiave di rendimento in JavaScript?


238

Ho sentito parlare di una parola chiave "yield" in JavaScript, ma ho trovato una documentazione molto scadente al riguardo. Qualcuno può spiegarmi (o raccomandare un sito che spiega) il suo utilizzo e a cosa serve?



4
è spiegato in MDN , ma penso che questo funzioni solo per Firefox, giusto? Quanto è portatile? Qualche modo per farlo su Chrome o node.js? PD: scusa, è Javascript v1.7 + , quindi questa è la proprietà da guardare quando cerchi supporto.
Trylks,

1
@ Trylks: i generatori sono disponibili nel nodo dal v0.11.2
Janus Troelsen il

@JanusTroelsen tuttavia, solo dietro una bandiera. Sono supportati in modo nativo in ioJS
Dan Pantry il

Risposte:


86

La documentazione MDN è abbastanza buona, IMO.

La funzione contenente la parola chiave yield è un generatore. Quando lo chiami, i suoi parametri formali sono legati ad argomenti reali, ma il suo corpo non è effettivamente valutato. Al contrario, viene restituito un generatore-iteratore. Ogni chiamata al metodo next () del generatore-iteratore esegue un altro passaggio attraverso l'algoritmo iterativo. Il valore di ogni passaggio è il valore specificato dalla parola chiave yield. Pensa al rendimento come alla versione generatore-iteratore di ritorno, indicando il confine tra ogni iterazione dell'algoritmo. Ogni volta che chiami next (), il codice del generatore riprende dall'istruzione che segue il rendimento.


2
@NicolasBarbulesco c'è un esempio molto ovvio se si fa clic sulla documentazione MDN.
Matt Ball,

@MattBall - una funzione come javascript per PI come questa sarebbe sufficiente come segue: function * PI {PI = ((Math.SQRT8;) / 9801;); } - o esiste già una funzione implementata in JavaScript per questo calcolo di PI?
dschinn1001,

4
Qual è il punto di citare MDN qui? Penso che tutti possano leggerlo su MDN. Visita davidwalsh.name/promises per saperne di più su di loro.
Ejaz Karim,

20
Come ha ottenuto ~ 80 voti quando (a) è una copia della "documentazione molto scadente" come la chiama l'interrogante e (b) non dice nulla di utile? Di seguito risposte molto migliori.
www-0av-Com,

4
se qualcuno chiede spiegazioni, basta semplicemente copiare e incollare una documentazione. Chiedere significa che hai già cercato nei documenti ma non li hai capiti.
Diego,

205

Risposte tardive, probabilmente yieldora tutti lo sanno , ma è arrivata una documentazione migliore.

Adattando un esempio di "Javascript's Future: Generators" di James Long per lo standard ufficiale Harmony:

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

"Quando chiami foo, ricevi un oggetto Generator che ha un metodo successivo."

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

Quindi yieldè un po 'come return: ottieni qualcosa in cambio. return xrestituisce il valore di x, ma yield xrestituisce una funzione, che fornisce un metodo per iterare verso il valore successivo. Utile se si dispone di una procedura potenzialmente intensiva di memoria che si potrebbe desiderare di interrompere durante l'iterazione.


13
Utile, ma immagino che sia function* foo(x){
Rana Deep

9
@RanaDeep: la sintassi della funzione viene estesa per aggiungere un token opzionale * . Se ne hai bisogno dipende dal tipo di futuro che stai tornando. Il dettaglio è lungo: GvR lo spiega per l'implementazione di Python , su cui è modellata l'implementazione di Javascript. L'uso function *sarà sempre corretto, sebbene in alcuni casi leggermente più sovraccarico che functioncon yield.
vescovo

1
@ Ajedi32 Sì, hai ragione. Harmony ha standardizzato la correlazione tra function *e yielde ha aggiunto l'errore citato ("Viene generato un errore iniziale se si verifica un'espressione di rendimento o rendimento * in una funzione non generatrice"). Ma l'implementazione originale di JavaScript 1.7 in Firefox non ha richiesto il* . Risposta aggiornata di conseguenza. Grazie!
vescovo,

3
@MuhammadUmer Js è finalmente diventato una lingua che puoi effettivamente usare. Si chiama evoluzione.
Lukas Liesis,

1
l'esempio è utile, ma ... cos'è una funzione *?
Diego,

66

È davvero semplice, ecco come funziona

  • yieldla parola chiave aiuta semplicemente a mettere in pausa e riprendere una funzione in qualsiasi momento in modo asincrono .
  • Inoltre aiuta a restituire valore da una funzione del generatore .

Prendi questa semplice funzione generatore :

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 ();

Finché non chiamate il _process.next () si wont eseguire le prime 2 righe di codice, allora il primo rendimento sarà sospendere la funzione. Per ripristinare la funzione fino al punto di pausa successivo ( parola chiave yield ) è necessario chiamare _process.next () .

Puoi pensare che i rendimenti multipli siano i punti di interruzione in un debugger javascript all'interno di una singola funzione. Fino a quando non si dice di navigare nel prossimo punto di interruzione, non eseguirà il blocco di codice. ( Nota : senza bloccare l'intera applicazione)

Ma mentre il rendimento esegue questa pausa e riprende comportamenti, può anche restituire alcuni risultati in {value: any, done: boolean} base alla funzione precedente non abbiamo emesso alcun valore. Se esploriamo l'output precedente, questo mostrerà lo stesso { value: undefined, done: false } con un valore non definito .

Consente di approfondire la parola chiave yield. Opzionalmente è possibile aggiungere espressione e impostare assegnare un valore opzionale predefinito . (Sintassi ufficiale del documento)

[rv] = yield [expression];

espressione : valore da restituire dalla funzione generatore

yield any;
yield {age: 12};

rv : restituisce il valore facoltativo passato al metodo next () del generatore

È possibile semplicemente passare i parametri alla funzione process () con questo meccanismo, per eseguire diverse parti di snervamento.

let val = yield 99; 

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

Provalo ora

usi

  • Valutazione pigra
  • Sequenze infinite
  • Flussi di controllo asincroni

Riferimenti:


54

Semplificando / elaborando la risposta di Nick Sotiros (che penso sia eccezionale), penso che sia meglio descrivere come si inizierebbe a scrivere codice yield.

A mio avviso, il più grande vantaggio dell'utilizzo yieldè che eliminerà tutti i problemi di callback nidificati che vediamo nel codice. All'inizio è difficile capire come, ed è per questo che ho deciso di scrivere questa risposta (per me e, si spera, per gli altri!)

Il modo in cui lo fa è introducendo l'idea di una co-routine, che è una funzione che può arrestare / mettere in pausa volontariamente fino a quando non ottiene ciò di cui ha bisogno. In javascript, questo è indicato da function*. Solo le function*funzioni possono usare yield.

Ecco alcuni javascript tipici:

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

Questo è ingombrante perché ora tutto il tuo codice (che ovviamente deve attendere questa loadFromDBchiamata) deve essere all'interno di questo brutto callback. Questo è male per alcuni motivi ...

  • Tutto il tuo codice è rientrato di un livello
  • Hai questo fine di })cui devi tenere traccia ovunque
  • Tutto questo function (err, result)gergo extra
  • Non è esattamente chiaro che lo stai facendo per assegnare un valore result

D'altra parte, con yield, tutto ciò può essere fatto in una riga con l'aiuto del simpatico framework di co-routine.

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

E così ora la tua funzione principale produrrà dove necessario quando dovrà attendere il caricamento di variabili e cose. Ma ora, per eseguire questo, è necessario chiamare una normale (funzione non coroutine). Un semplice framework di routine può risolvere questo problema in modo che tutto ciò che devi fare è eseguire questo:

start(main())

E l'inizio è definito (dalla risposta di 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
        });
    }
}

E ora, puoi avere un bellissimo codice che è molto più leggibile, facile da eliminare e senza bisogno di giocherellare con rientri, funzioni, ecc.

Un'osservazione interessante è che in questo esempio, in yieldrealtà è solo una parola chiave che puoi mettere prima di una funzione con un callback.

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

Stampa "Hello World". Quindi puoi effettivamente trasformare qualsiasi funzione di callback in yieldsemplicemente creando la stessa firma della funzione (senza la cb) e ritornando function (cb) {}, in questo modo:

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

Spero che con questa conoscenza sia possibile scrivere codice più pulito, più leggibile e facile da eliminare !


a function*è solo una funzione regolare senza rendimento?
Abdul

Penso che intendi dire che function *è una funzione che contiene rendimento. È una funzione speciale chiamata generatore.
Leander,

7
Per le persone che usano già yieldovunque, sono sicuro che questo ha più senso dei callback, ma non riesco a vedere come questo sia più leggibile dei callback.
Palswim,

quell'articolo è difficile da capire
Martian2049

18

Per dare una risposta completa: yieldfunziona in modo simile a return, ma in un generatore.

Per quanto riguarda l'esempio comunemente fornito, funziona come segue:

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

Ma c'è anche un secondo scopo della parola chiave yield. Può essere utilizzato per inviare valori al generatore.

Per chiarire, un piccolo esempio:

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

Funziona, come 2viene assegnato il valore y, inviandolo al generatore, dopo che si è fermato alla prima resa (che è tornata 0).

Questo ci consente di fare cose davvero funky. (cerca la coroutine)



6

yield può anche essere usato per eliminare l'inferno del callback, con un framework coroutine.

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

Generatore di sequenze di Fibonacci utilizzando la parola chiave yield.

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 parola chiave nella funzione javaScript lo rende generatore,

cos'è il generatore in javaScript?

Un generatore è una funzione che produce una sequenza di risultati anziché un singolo valore, ovvero si genera una serie di valori

I generatori di significato ci aiutano a lavorare in modo asincrono con gli iteratori di aiuto, oh ora quali sono gli iteratori di hacking? veramente?

Gli iteratori sono mezzi attraverso i quali siamo in grado di accedere agli elementi uno alla volta

da dove l'iteratore ci aiuta ad accedere all'elemento uno alla volta? ci aiuta ad accedere agli oggetti tramite le funzioni del generatore,

le funzioni del generatore sono quelle in cui utilizziamo la yeildparola chiave, la parola chiave yield ci aiuta a mettere in pausa e riprendere l'esecuzione della funzione

ecco un esempio veloce

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

fammi spiegare brevemente cosa sta succedendo

hai notato che l'esecuzione è in pausa per ogni yeildparola chiave e siamo in grado di accedervi prima yieldcon l'aiuto dell'iteratore.next()

questo scorre a tutte le yieldparole chiave una alla volta e quindi restituisce indefinito quando non ci sono più yieldparole chiave rimaste in parole semplici che puoi direyield parola chiave è punto di interruzione in cui la funzione ogni volta si interrompe e riprende solo quando la si chiama usando iteratore

per il nostro caso: _getMeDrink.next()questo è un esempio di iteratore che ci sta aiutando ad accedere a ciascun punto di interruzione nella funzione

Esempio di generatori: async/await

se vedi l'implementazione async/await vedrai che generator functions & promisessono usati per far async/awaitfunzionare

si prega di sottolineare che eventuali suggerimenti sono i benvenuti


3

Dipendenza tra chiamate javascript asincrone.

Un altro buon esempio di come si può usare la resa.

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

Prima di conoscere la resa è necessario conoscere i generatori. I generatori vengono creati utilizzando la function*sintassi. Le funzioni del generatore non eseguono il codice ma restituiscono invece un tipo di iteratore chiamato generatore. Quando viene dato un valore usando il nextmetodo, la funzione del generatore continua ad essere eseguita fino a quando non incontra una parola chiave yield. L'utilizzo yieldrestituisce un oggetto contenente due valori, uno è valore e l'altro è fatto (booleano). Il valore può essere un array, un oggetto ecc.


0

Un semplice esempio:

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

Sto anche cercando di capire la parola chiave yield. Sulla base della mia attuale comprensione, in generatore, la parola chiave yield funziona come un interruttore di contesto CPU. Quando viene eseguita l'istruzione yield, vengono salvati tutti gli stati (ad esempio, le variabili locali).

Oltre a questo, un oggetto risultato diretto verrà restituito al chiamante, come {valore: 0, fatto: falso}. Il chiamante può utilizzare questo oggetto risultato per decidere se 'riattivare' nuovamente il generatore chiamando next () (chiamare next () significa iterare l'esecuzione).

Un'altra cosa importante è che può impostare un valore su una variabile locale. Questo valore può essere passato dal chiamante "next ()" quando "si sveglia" il generatore. ad esempio, it.next ('valueToPass'), in questo modo: "resultValue = yield slowQuery (1);" Proprio come quando si sveglia una prossima esecuzione, il chiamante può iniettare un risultato in esecuzione nell'esecuzione (iniettandolo nella variabile locale). Pertanto, per questa esecuzione, esistono due tipi di stato:

  1. il contesto salvato nell'ultima esecuzione.

  2. I valori iniettati dal trigger di questa esecuzione.

Quindi, con questa funzione, il generatore può risolvere più operazioni asincrone. Il risultato della prima query asincrona verrà passato alla seconda impostando la variabile locale (resultValue nell'esempio sopra). La seconda query asincrona può essere attivata solo dalla risposta della prima query asincrona. Quindi la seconda query asincrona può controllare il valore della variabile locale per decidere i passi successivi perché la variabile locale è un valore iniettato dalla risposta della prima query.

Le difficoltà delle query asincrone sono:

  1. callback inferno

  2. perdita di contesto a meno che non vengano passati come parametri nel callback.

resa e generatore possono aiutare su entrambi.

Senza rendimento e generatore, l'ordinamento di più query asincrone richiede una richiamata nidificata con parametri come contesto che non è facile da leggere e gestire.

Di seguito è riportato un esempio di query asincrone concatenate in esecuzione 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+++++++++++");

Di seguito è riportato il risultato corrente:

+++++++++++ iniziare +++++++++++

query1 0

+++++++++++ fine +++++++++++

query2 1

query4 0

Il modello di stato sotto può fare la stessa cosa per l'esempio sopra:

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+++++++++++");

Di seguito è riportato il risultato corrente:

+++++++++++ iniziare +++++++++++

query1 0

+++++++++++ fine +++++++++++

query2 1

query4 0


0

non dimenticare l'utilissima sintassi "x del generatore" per scorrere il generatore. Non è necessario utilizzare la funzione next ().

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);
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.