Ouvi falar de uma palavra-chave "yield" em JavaScript, mas achei uma documentação muito ruim. Alguém pode me explicar (ou recomendar um site que explique) seu uso e para que é utilizado?
Ouvi falar de uma palavra-chave "yield" em JavaScript, mas achei uma documentação muito ruim. Alguém pode me explicar (ou recomendar um site que explique) seu uso e para que é utilizado?
Respostas:
A documentação MDN é muito boa, IMO.
A função que contém a palavra-chave yield é um gerador. Quando você o chama, seus parâmetros formais são vinculados a argumentos reais, mas seu corpo não é realmente avaliado. Em vez disso, um gerador-iterador é retornado. Cada chamada para o método next () do gerador-iterador executa outra passagem pelo algoritmo iterativo. O valor de cada etapa é o valor especificado pela palavra-chave yield. Pense em yield como a versão de retorno gerador-iterador, indicando o limite entre cada iteração do algoritmo. Cada vez que você chama next (), o código do gerador retoma da instrução após o rendimento.
Resposta tardia, provavelmente todo mundo sabe yield
agora, mas chegou uma documentação melhor.
Adaptando um exemplo de "Javascript's Future: Generators", de James Long, para o padrão oficial Harmony:
function * foo(x) {
while (true) {
x = x * 2;
yield x;
}
}
"Quando você chama foo, você retorna um objeto Generator que tem um próximo método."
var g = foo(2);
g.next(); // -> 4
g.next(); // -> 8
g.next(); // -> 16
Então yield
é como return
: você recebe algo de volta. return x
retorna o valor de x
, mas yield x
retorna uma função, que fornece um método para iterar em direção ao próximo valor. Útil se você tiver um procedimento com uso intensivo de memória que talvez queira interromper durante a iteração.
function* foo(x){
não
*
. Se você precisa ou não, depende do tipo de futuro que você está retornando. Os detalhes são longos: o GvR explica para a implementação do Python , na qual a implementação do Javascript é modelada. O uso function *
sempre estará correto, embora em alguns casos um pouco mais sobrecarga do que function
com yield
.
function *
e yield
, e adicionou o erro entre aspas ("Um erro inicial é gerado se uma expressão de rendimento ou rendimento * ocorrer em uma função não geradora"). Mas, a implementação original do Javascript 1.7 no Firefox não exigia o*
. Resposta atualizada em conformidade. Obrigado!
É realmente simples, é assim que funciona
yield
A palavra-chave simplesmente ajuda a pausar e retomar uma função a qualquer momento de forma assíncrona .Tome esta função simples de gerador :
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');
}
deixe _process = process ();
Até você chamar o _process.next (), ele não executará as 2 primeiras linhas de código, e o primeiro rendimento fará uma pausa na função. Para retomar a função até o próximo ponto de pausa ( palavra-chave yield ), você precisa chamar _process.next () .
Você pode pensar que vários rendimentos são os pontos de interrupção em um depurador javascript dentro de uma única função. Até que você diga para navegar no próximo ponto de interrupção, ele não executará o bloco de código. ( Nota : sem bloquear todo o aplicativo)
Mas enquanto o yield realiza essa pausa e retoma os comportamentos, ele também pode retornar alguns resultados , de {value: any, done: boolean}
acordo com a função anterior, não emitimos nenhum valor. Se explorarmos a saída anterior, ela mostrará o mesmo { value: undefined, done: false }
com o valor indefinido .
Permite digitar a palavra-chave yield. Opcionalmente, você pode adicionar expressão e definir um valor opcional padrão . (Sintaxe oficial do documento)
[rv] = yield [expression];
expressão : Valor a ser retornado da função do gerador
yield any;
yield {age: 12};
rv : Retorna o valor opcional que passou para o método next () do gerador
Simplesmente você pode passar parâmetros para a função process () com esse mecanismo, para executar diferentes partes de produção.
let val = yield 99;
_process.next(10);
now the val will be 10
Usos
Referências:
Simplificando / elaborando a resposta de Nick Sotiros (o que eu acho incrível), acho que é melhor descrever como alguém começaria a codificar yield
.
Na minha opinião, a maior vantagem do uso yield
é que ele eliminará todos os problemas de retorno de chamada aninhados que vemos no código. É difícil ver como, a princípio, foi por isso que decidi escrever essa resposta (para mim e para outras pessoas!)
A maneira como faz isso é introduzindo a idéia de uma co-rotina, que é uma função que pode parar / pausar voluntariamente até conseguir o que precisa. Em javascript, isso é indicado por function*
. Somente function*
funções podem ser usadas yield
.
Aqui está um javascript típico:
loadFromDB('query', function (err, result) {
// Do something with the result or handle the error
})
Isso é desajeitado porque agora todo o seu código (que obviamente precisa aguardar essa loadFromDB
ligação) precisa estar dentro desse retorno de chamada feio. Isso é ruim por alguns motivos ...
})
que precisa acompanhar em todos os lugaresfunction (err, result)
jargão extraresult
Por outro lado, com yield
, tudo isso pode ser feito em uma linha com a ajuda da boa estrutura co-rotineira.
function* main() {
var result = yield loadFromDB('query')
}
E agora sua função principal renderá sempre que necessário quando precisar esperar que variáveis e coisas sejam carregadas. Mas agora, para executar isso, você precisa chamar uma função normal (não relacionada à corotina). Uma estrutura co-rotineira simples pode corrigir esse problema e tudo o que você precisa fazer é executar o seguinte:
start(main())
E o começo é definido (da resposta 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
});
}
}
E agora, você pode ter um código bonito que é muito mais legível, fácil de excluir e sem necessidade de mexer com recuos, funções etc.
Uma observação interessante é que, neste exemplo, yield
na verdade é apenas uma palavra-chave que você pode colocar antes de uma função com retorno de chamada.
function* main() {
console.log(yield function(cb) { cb(null, "Hello World") })
}
Imprimia "Olá Mundo". Assim, você pode realmente transformar qualquer função de retorno de chamada usando yield
simplesmente criando a mesma assinatura de função (sem o cb) e retornando da seguinte function (cb) {}
forma:
function yieldAsyncFunc(arg1, arg2) {
return function (cb) {
realAsyncFunc(arg1, arg2, cb)
}
}
Felizmente, com esse conhecimento, você pode escrever um código mais limpo e legível, fácil de excluir !
function*
é apenas uma função regular sem rendimento?
function *
é uma função que contém rendimento. É uma função especial chamada gerador.
yield
qualquer lugar, tenho certeza de que isso faz mais sentido do que os retornos de chamada, mas não vejo como isso é mais legível do que os retornos de chamada.
Para dar uma resposta completa: yield
está funcionando de maneira semelhante a return
, mas em um gerador.
Quanto ao exemplo comum, isso funciona da seguinte maneira:
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
Mas há também um segundo objetivo da palavra-chave yield. Pode ser usado para enviar valores ao gerador.
Para esclarecer, um pequeno exemplo:
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
Isso funciona, conforme o valor 2
é atribuído y
, enviando-o ao gerador, depois que ele parou no primeiro rendimento (que retornou 0
).
Isso nos permite fazer algumas coisas realmente descoladas. (procure corotina)
É usado para geradores de iteradores. Basicamente, ele permite que você faça uma sequência (potencialmente infinita) usando código de procedimento. Veja a documentação do Mozilla .
yield
também pode ser usado para eliminar o inferno de retorno de chamada, com uma estrutura de corotina.
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());
Gerador de sequência de Fibonacci usando a palavra-chave 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
A palavra-chave na função javaScript o torna gerador,
o que é gerador em javaScript?
Um gerador é uma função que produz uma sequência de resultados em vez de um único valor, ou seja, você gera uma série de valores
Os geradores de significado nos ajudam a trabalhar de forma assíncrona com os iteradores de ajuda. Agora, o que são os iteradores de hack? realmente?
Iteradores são meios através dos quais podemos acessar itens um de cada vez
de onde o iterador nos ajuda a acessar o item um de cada vez? nos ajuda a acessar itens através das funções do gerador,
funções geradoras são aquelas nas quais usamos yeild
palavras-chave, rendimento palavra-chave nos ajuda a pausar e retomar a execução da função
aqui está um exemplo 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
deixe-me explicar rapidamente o que está acontecendo
você notou que a execução está sendo pausada em cada yeild
palavra - chave e podemos acessar primeiro yield
com a ajuda do iterador.next()
isso itera para todas as yield
palavras-chave uma de cada vez e, em seguida, retorna indefinido quando não há mais yield
palavras - chave em palavras simples. Você pode dizer que a yield
palavra-chave é um ponto de interrupção em que a função é interrompida a cada vez e é retomada apenas quando é chamada pelo iterador
para o nosso caso: _getMeDrink.next()
este é um exemplo de iterador que está nos ajudando a acessar cada ponto de interrupção na função
Exemplo de Geradores:
async/await
se você ver a implementação de async/await
você verá que generator functions & promises
são usados para fazer o async/await
trabalho
por favor, aponte todas as sugestões são bem-vindas
Dependência entre chamadas assíncronas em javascript.
Outro bom exemplo de como o rendimento pode ser usado.
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()
Antes de aprender sobre o rendimento, você precisa conhecer os geradores. Geradores são criados usando a function*
sintaxe. As funções do gerador não executam código, mas retornam um tipo de iterador chamado gerador. Quando um valor é fornecido usando o next
método, a função do gerador continua em execução até encontrar uma palavra-chave yield. Usar yield
retorna um objeto contendo dois valores, um é o valor e o outro é feito (booleano). O valor pode ser uma matriz, objeto etc.
Um exemplo simples:
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}
Também estou tentando entender a palavra-chave yield. Com base no meu entendimento atual, no gerador, a palavra-chave yield funciona como uma alternância de contexto da CPU. Quando a declaração de rendimento é executada, todos os estados (por exemplo, variáveis locais) são salvos.
Além disso, um objeto de resultado direto será retornado ao chamador, como {value: 0, done: false}. O chamador pode usar esse objeto de resultado para decidir se deseja 'ativar' o gerador novamente chamando next () (chamar next () é para iterar a execução).
Outra coisa importante é que ele pode definir um valor para uma variável local. Este valor pode ser passado pelo chamador 'next ()' ao 'acordar' o gerador. por exemplo, it.next ('valueToPass'), assim: "resultValue = yield slowQuery (1);" Assim como ao ativar uma próxima execução, o chamador pode injetar algum resultado em execução na execução (injetando-o na variável local). Assim, para esta execução, existem dois tipos de estado:
o contexto que foi salvo na última execução.
Os valores injetados pelo gatilho desta execução.
Portanto, com esse recurso, o gerador pode classificar várias operações assíncronas. O resultado da primeira consulta assíncrona será passado para a segunda, definindo a variável local (resultValue no exemplo acima). A segunda consulta assíncrona só pode ser acionada pela resposta da primeira consulta assíncrona. Em seguida, a segunda consulta assíncrona pode verificar o valor da variável local para decidir as próximas etapas, porque a variável local é um valor injetado da resposta da primeira consulta.
As dificuldades das consultas assíncronas são:
inferno de retorno de chamada
perda de contexto, a menos que sejam transmitidos como parâmetros no retorno de chamada.
rendimento e gerador podem ajudar em ambos.
Sem rendimento e gerador, para classificar várias consultas assíncronas, é necessário um retorno de chamada aninhado com parâmetros como contexto, o que não é fácil de ler e manter.
Abaixo está um exemplo de consultas assíncronas encadeadas que são executadas com o 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+++++++++++");
Abaixo está o resultado atual:
+++++++++++ start +++++++++++
query1 0
+++++++++++ end +++++++++++
query2 1
query4 0
O padrão de estado abaixo pode fazer o mesmo no exemplo acima:
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+++++++++++");
A seguir, o resultado da execução:
+++++++++++ start +++++++++++
query1 0
+++++++++++ end +++++++++++
query2 1
query4 0
não esqueça a sintaxe muito útil do 'x do gerador' para percorrer o gerador. Não é necessário usar a função 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);
}