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?
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?
Risposte:
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.
Risposte tardive, probabilmente yield
ora 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 x
restituisce il valore di x
, ma yield x
restituisce 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.
function* foo(x){
lì
*
. 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 function
con yield
.
function *
e yield
e 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!
È davvero semplice, ecco come funziona
yield
la parola chiave aiuta semplicemente a mettere in pausa e riprendere una funzione in qualsiasi momento in modo asincrono .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
usi
Riferimenti:
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 loadFromDB
chiamata) deve essere all'interno di questo brutto callback. Questo è male per alcuni motivi ...
})
cui devi tenere traccia ovunquefunction (err, result)
gergo extraresult
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 yield
realtà è 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 yield
semplicemente 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 !
function*
è solo una funzione regolare senza rendimento?
function *
è una funzione che contiene rendimento. È una funzione speciale chiamata generatore.
yield
ovunque, sono sicuro che questo ha più senso dei callback, ma non riesco a vedere come questo sia più leggibile dei callback.
Per dare una risposta completa: yield
funziona 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 2
viene 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)
È usato per i generatori di iteratori. Fondamentalmente, ti permette di fare una sequenza (potenzialmente infinita) usando il codice procedurale. Vedi la documentazione di Mozilla .
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());
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
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 yeild
parola 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 yeild
parola chiave e siamo in grado di accedervi prima yield
con l'aiuto dell'iteratore.next()
questo scorre a tutte le yield
parole chiave una alla volta e quindi restituisce indefinito quando non ci sono più yield
parole 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 & promises
sono usati per far async/await
funzionare
si prega di sottolineare che eventuali suggerimenti sono i benvenuti
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()
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 next
metodo, la funzione del generatore continua ad essere eseguita fino a quando non incontra una parola chiave yield. L'utilizzo yield
restituisce un oggetto contenente due valori, uno è valore e l'altro è fatto (booleano). Il valore può essere un array, un oggetto ecc.
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}
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:
il contesto salvato nell'ultima esecuzione.
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:
callback inferno
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
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);
}