Qual é a palavra-chave yield em JavaScript?


238

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?


Ele provavelmente significa 'Rendimento' bytes.com/topic/python/answers/685510-yield-keyword-usage
ant

4
é explicado no MDN , mas acho que isso só funciona para o Firefox, certo? Quão portátil é? Existe alguma maneira de fazer isso no Chrome ou node.js? PD: desculpe, é Javascript v1.7 + , então essa é a propriedade a ser observada ao procurar suporte.
Trylks

1
@Trylks: Os geradores estão disponíveis no Node desde a v0.11.2
Janus Troelsen

@JanusTroelsen no entanto, apenas atrás de uma bandeira. Eles são suportados nativamente no ioJS
Dan Pantry

Respostas:


86

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.


2
@NicolasBarbulesco, existe um exemplo muito óbvio se você clicar na documentação do MDN.
Matt Ball

@MattBall - seria suficiente uma função como javascript para PI como esta: function * PI {PI = ((Math.SQRT8;) / 9801;); } - ou já existe uma função implementada em javascript para esse cálculo do PI?
dschinn1001

4
Qual é o objetivo de citar o MDN aqui? Acho que todos podem ler isso no MDN. Visite davidwalsh.name/promises para saber mais sobre eles.
Ejaz Karim

20
Como isso obteve 80 votos positivos quando (a) é uma cópia da "documentação muito pobre", como o questionador chama e (b) não diz nada de útil? Respostas muito melhores abaixo.
www-0av-Com

4
se alguém pedir explicações, basta copiar e colar uma documentação é totalmente inútil. Pedir significa que você já pesquisou nos documentos, mas não os entendeu.
197 Diego Diego

205

Resposta tardia, provavelmente todo mundo sabe yieldagora, 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 xretorna o valor de x, mas yield xretorna 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.


13
Útil, mas eu acho que você sua function* foo(x){não
Rana Profundo

9
@RanaDeep: a sintaxe da função é estendida para adicionar um token opcional * . 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 functioncom yield.
bispo

1
@ Ajedi32 Sim, você está certo. O Harmony padronizou a correlação entre 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!
bispo

3
O @MuhammadUmer Js finalmente se torna um idioma que você pode realmente usar. Isso se chama evolução.
Lukas Liesis

1
exemplo é útil, mas ... o que é uma função *?
217 Diego Diego

66

É realmente simples, é assim que funciona

  • yieldA palavra-chave simplesmente ajuda a pausar e retomar uma função a qualquer momento de forma assíncrona .
  • Além disso, ajuda a retornar valor de uma função de gerador .

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 

Tente agora

Usos

  • Avaliação preguiçosa
  • Sequências infinitas
  • Fluxos de controle assíncrono

Referências:


54

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 loadFromDBligação) precisa estar dentro desse retorno de chamada feio. Isso é ruim por alguns motivos ...

  • Todo o seu código é recuado um nível em
  • Você tem esse fim })que precisa acompanhar em todos os lugares
  • Todo esse function (err, result)jargão extra
  • Não é exatamente claro que você está fazendo isso para atribuir um valor a result

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, yieldna 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 yieldsimplesmente 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 !


a function*é apenas uma função regular sem rendimento?
Abdul

Eu acho que você quer dizer que function *é uma função que contém rendimento. É uma função especial chamada gerador.
Leander

7
Para pessoas que já usam em yieldqualquer 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.
palswim

que o artigo é difícil de entender
Martian2049

18

Para dar uma resposta completa: yieldestá 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)



6

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

4

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 

4

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 yeildpalavras-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 yeildpalavra - chave e podemos acessar primeiro yieldcom a ajuda do iterador.next()

isso itera para todas as yieldpalavras-chave uma de cada vez e, em seguida, retorna indefinido quando não há mais yieldpalavras - chave em palavras simples. Você pode dizer que a yieldpalavra-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 & promisessão usados ​​para fazer o async/awaittrabalho

por favor, aponte todas as sugestões são bem-vindas


3

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


0

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 nextmétodo, a função do gerador continua em execução até encontrar uma palavra-chave yield. Usar yieldretorna um objeto contendo dois valores, um é o valor e o outro é feito (booleano). O valor pode ser uma matriz, objeto etc.


0

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}

0

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:

  1. o contexto que foi salvo na última execução.

  2. 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:

  1. inferno de retorno de chamada

  2. 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


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);
}
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.