JavaScript中的yield关键字是什么?


237

我听说过JavaScript中有一个“ yield”关键字,但是我发现关于它的文档很差。有人可以向我解释(或推荐一个可以解释其用途的网站)及其用途吗?



4
它在MDN中进行了解释,但是我认为这仅适用于Firefox,对吗?它的便携性如何?在Chrome或node.js上有什么办法吗?PD:抱歉,它是Javascript v1.7 +,因此这是寻求支持时要考虑的属性。
Trylks 2012年

1
@Trylks:自v0.11.2版本起,Node中便提供了生成器
Janus Troelsen,

但是,@ JanusTroelsen仅在标志后面。它们在ioJS中得到本地支持
Dan Pantry

Answers:


85

IMO ,MDN文档非常好。

包含yield关键字的函数是一个生成器。当您调用它时,它的形式参数绑定到实际参数,但是它的主体没有被实际评估。相反,将返回generator-iterator。对生成器迭代器的next()方法的每次调用都会通过迭代算法执行另一遍。每个步骤的值是yield关键字指定的值。将yield视为返回的生成器-迭代器版本,指示算法每次迭代之间的边界。每次调用next()时,生成器代码都会从yield之后的语句恢复。


2
@NicolasBarbulesco,如果单击MDN文档,则有一个非常明显的示例。
马特·鲍尔

@MattBall-像这样的用于PI的javascript函数足以满足以下要求:function * PI {PI =((Math.SQRT8;)/ 9801;); }-或已经在JavaScript中实现了用于计算PI的函数?
dschinn1001 2015年

4
在这里引用MDN有什么意义?我认为每个人都可以在MDN上阅读该内容。访问davidwalsh.name/promises,以了解有关它们的更多信息。
Ejaz Karim

20
(a)它是发问者称其为“非常差劲的文档”的副本,并且(b)没有任何帮助的情况下,如何获得〜80的投票?下面的答案更好。
www-0av-Com

4
如果有人要求解释,只需复制粘贴文档是完全没有用的。询问意味着您已经在文档中搜索过,但是您不理解它们。
迭戈

205

答案很晚,也许每个人都知道yield,但是随之而来的是一些更好的文档。

改编James Long的“ Javascript的未来:生成器”中的示例作为官方Harmony标准:

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

“调用foo时,您将返回具有下一个方法的Generator对象。”

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

所以yield是有点像return:你得到的东西回来。 return x返回的值x,但yield x返回一个函数,该函数为您提供了一个方法来迭代下一个值。如果您有可能要在迭代过程中中断的可能占用大量内存的过程,则很有用。


13
有用,但我想您在function* foo(x){那儿
Rana Deep

9
@RanaDeep:扩展了函数语法,以添加可选 *标记。是否需要它取决于您所返回的未来类型。细节很长:GvR在Python实现中对此进行了解释,并以此为基础对Javascript实现进行建模。使用function *始终是正确的,尽管在某些情况下,开销要比function使用稍微多一些yield
主教

1
@ Ajedi32是的,你是对的。Harmony标准化了function *和之间的相关性yield,并添加了引用的错误(“如果在非生成器函数中出现yield或yield *表达式,则会引起早期错误”)。但是,Firefox中原始的Javascript 1.7实现不需要*。相应地更新了答案。谢谢!
主教

3
@MuhammadUmer Js最终成为您可以实际使用的语言。这就是所谓的进化。
Lukas Liesis '16

1
示例很有用,但是...有什么功能*?
迭戈

65

真的很简单,就是这样

  • yield关键字只是简单地帮助您随时异步暂停恢复功能。
  • 另外,它有助于从生成器函数返回值

使用这个简单的生成器函数:

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');
}

让_process = process();

在调用_process.next()之前,不会执行代码的前2行,然后第一个yield暂停该函数。要将函数恢复到下一个暂停点(yield关键字),您需要调用_process.next()

您可以认为多个收益是单个函数中javascript调试器中的断点。在您告诉导航下一个断点之前,它不会执行代码块。(注意:不阻塞整个应用程序)

但是,尽管yield执行此暂停和恢复行为时,它也可以 根据以前的函数返回一些结果,而{value: any, done: boolean}我们没有发出任何值。如果我们探索先前的输出,它将显示相同{ value: undefined, done: false } 的值undefined

让我们深入到yield关键字。(可选)您可以添加表达式并设置分配默认的可选值。(官方文档语法)

[rv] = yield [expression];

expression:从生成器函数返回的值

yield any;
yield {age: 12};

rv:返回传递给生成器的next()方法的可选值

只需使用此机制即可将参数传递给process()函数,以执行不同的屈服部分。

let val = yield 99; 

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

现在就试试

用法

  • 懒惰评估
  • 无限序列
  • 异步控制流

参考文献:


54

简化/详细说明尼克·索蒂罗斯(Nick Sotiros)的答案(我认为这是很棒的),我认为最好描述一下如何开始使用 yield

我认为,使用 yield是它将消除我们在代码中看到的所有嵌套回调问题。一开始很难看,这就是为什么我决定写这个答案的原因(对我自己,对其他人也是如此!)

它的工作方式是引入协同例程的概念,该例程可以自愿停止/暂停直到获得所需的功能。在javascript中,用表示function*。只有function*功能可以使用yield

这是一些典型的javascript:

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

这很笨拙,因为现在您的所有代码(显然需要等待此loadFromDB调用)都必须位于此难看的回调中。这有几个原因是不好的...

  • 您的所有代码都缩进到一个级别
  • 您有这个目的}),需要随时随地跟踪
  • 所有这些额外的function (err, result)行话
  • 不太清楚您正在执行此操作以将值分配给 result

另一方面,通过使用yield,可以在漂亮的协同例程框架的帮助下在一行中完成所有这些操作。

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

因此,现在,当需要等待变量和事物加载时,主函数将在需要时产生。但是现在,为了运行此命令,您需要调用一个普通的(非协程函数)。一个简单的协同例程框架可以解决此问题,因此您只需执行以下操作:

start(main())

并定义了开始(来自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
        });
    }
}

现在,您可以拥有漂亮的代码,这些代码更具可读性,易于删除,并且无需摆弄缩进,函数等。

有趣的观察是,在此示例中,yield实际上只是可以在带有回调的函数之前放置的关键字。

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

将打印“ Hello World”。因此,您实际上可以yield通过简单地创建相同的函数签名(不带cb)并返回来使用任何回调函数function (cb) {},如下所示:

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

希望有了这些知识,您可以编写更清晰,更易读的易于删除的代码!


一个function*只是没有收益的正则函数?
阿卜杜勒

我认为您的意思function *包含 yield 的函数。这是一个特殊功能,称为生成器。
莱安德

7
对于已经yield在任何地方使用过的人来说,我相信这比回调更有意义,但是我看不出它比回调更易读。
palswim's

那篇文章很难理解
Martian2049 '18

18

给出完整的答案:yield与相似return,但在生成器中工作。

对于通常给出的示例,其工作方式如下:

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

但是yield关键字还有第二个目的。它可以用于将值发送到生成器。

为了澄清,一个小例子:

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

在将值停在第一个收益率(返回)后,通过将其发送给生成器,将值2分配给y,这是可行的0

这使我们能够进行一些非常时髦的事情。(查找协程)



6

yield 也可以通过协程框架来消除回调地狱。

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

使用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 javaScript函数中的关键字使其成为生成器,

javaScript中的生成器是什么?

生成器是一种生成结果序列而不是单个值的函数,即您生成一系列值

含义生成器可以帮助我们与帮助迭代器异步工作,哦,现在,hack迭代器是什么?真?

迭代器是指我们能够一次访问一个项目的意思

从哪里迭代器可以帮助我们一次访问一项?它帮助我们通过生成器功能访问项目,

生成器函数是使用yeild关键字的函数,yield关键字可帮助我们暂停和恢复函数的执行

这是一个简单的例子

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

让我飞扬一下,这是怎么回事

您注意到每个yeild关键字的执行都已暂停,我们可以yield在迭代器的帮助下首先访问.next()

yield一次一次迭代所有关键字,然后yield在简单单词中不再有关键字时返回undefined,您可以说yield关键字是断点,函数每次暂停时仅在使用迭代器调用时才恢复

对于我们的情况:_getMeDrink.next()这是迭代器的示例,它可以帮助我们访问函数中的每个断点

发电机示例: async/await

如果您看到实施,async/await 您将看到generator functions & promises用于制作async/await工作

请指出任何建议,欢迎


3

异步javascript调用之间的依赖关系。

如何使用产量的另一个很好的例子。

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

在了解产量之前,您需要了解发电机。生成器是使用function*语法创建的。生成器函数不执行代码,而是返回一种称为生成器的迭代器类型。当使用next方法给定值时,生成器函数将继续执行,直到遇到yield关键字为止。使用yield会给您返回一个包含两个值的对象,一个是value,另一个是完成的(布尔值)。该值可以是数组,对象等。


0

一个简单的例子:

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

我也试图了解yield关键字。根据我目前的理解,在生成器中,yield关键字的工作方式类似于CPU上下文切换。运行yield语句时,将保存所有状态(例如,局部变量)。

除此之外,直接结果对象将返回给调用者,例如{value:0,done:false}。调用者可以使用此结果对象来决定是否通过调用next()再次“唤醒”生成器(调用next()是要迭代执行)。

另一个重要的事情是它可以将值设置为局部变量。“唤醒”生成器时,“ next()”调用者可以传递此值。例如it.next('valueToPass'),例如:“ resultValue = yield slowQuery(1);” 就像唤醒下一个执行时一样,调用者可以向执行中注入一些运行结果(将其注入局部变量)。因此,对于此执行,存在两种状态:

  1. 上次执行时保存的上下文。

  2. 由该执行的触发器注入的值。

因此,借助此功能,生成器可以整理出多个异步操作。通过设置局部变量(上例中的resultValue),将第一个异步查询的结果传递给第二个异步查询。第二个异步查询只能由第一个异步查询的响应触发。然后,第二个异步查询可以检查局部变量值来决定下一步,因为该局部变量是从第一个查询的响应中注入的值。

异步查询的困难在于:

  1. 回调地狱

  2. 除非在回调中将它们作为参数传递,否则它们会失去上下文。

产量和发电机都可以提供帮助。

如果没有yield和generator,要解决多个异步查询问题,需要使用参数作为上下文的嵌套回调,这不容易阅读和维护。

下面是一个与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+++++++++++");

运行结果如下:

+++++++++++开始+++++++++++

查询1 0

+++++++++++结束+++++++++++

查询2 1

query4 0

下面的状态模式可以为上面的示例做类似的事情:

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

以下是运行结果:

+++++++++++开始+++++++++++

查询1 0

+++++++++++结束+++++++++++

查询2 1

query4 0


0

不要忘记非常有用的“ x of generator”语法来循环遍历生成器。完全不需要使用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);
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.