【问题标题】:What's the yield keyword in JavaScript?JavaScript 中的 yield 关键字是什么?
【发布时间】:2011-01-17 22:28:42
【问题描述】:

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

【问题讨论】:

  • 它在MDN 中有解释,但我认为这只适用于firefox,对吧?它的便携性如何?在 Chrome 或 node.js 上有什么办法吗? PD:抱歉,它是 Javascript v1.7+,所以这是在寻求支持时要查看的属性。
  • @Trylks:生成器从 v0.11.2 开始在 Node 中可用
  • @JanusTroelsen 然而,只是在一个标志后面。 ioJS 原生支持它们
  • 注意: Internet Explorer 不支持yield

标签: javascript yield keyword


【解决方案1】:

迟到的回答,现在可能每个人都知道yield,但已经出现了一些更好的文档。

将 James Long 的 "Javascript's Future: Generators" 中的示例改编为官方 Harmony 标准:

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

"当你调用 foo 时,你会得到一个 Generator 对象,它有一个 next 方法。”

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

所以yield 有点像return:你会得到一些回报。 return x 返回 x 的值,但 yield x 返回一个函数,它为您提供了一种迭代下一个值的方法。如果您有一个potentially memory intensive procedure,您可能希望在迭代期间中断该potentially memory intensive procedure,这很有用。

【讨论】:

  • 有帮助,但我猜你是 function* foo(x){ 那里
  • @RanaDeep:The function syntax is extended to add an optional * token。您是否需要它取决于您返回的未来类型。细节很长:GvR explains it for the Python implementation,Javascript 实现在此基础上建模。使用function * 总是正确的,尽管在某些情况下比使用yieldfunction 开销略多。
  • @Ajedi32 是的,你是对的。 Harmony 标准化了 function *yield 之间的相关性,并添加了引用的错误(“如果在非生成器函数中出现 yield 或 yield* 表达式,则会引发早期错误”)。但是,Firefox didn't require the * 中的原始 Javascript 1.7 实现。相应地更新了答案。谢谢!
  • @MuhammadUmer Js 终于成为一种你可以实际使用的语言。这叫做进化。
  • 示例很有用,但是...什么是函数 * ?
【解决方案2】:

真的很简单,这就是它的工作原理

  • yield 关键字只是有助于异步随时暂停恢复函数。
  • 此外,它还有助于从 生成器函数 返回值

使用这个简单的generator函数:

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 行代码,那么first yield暂停函数。 要恢复函数直到下一个暂停点(yield关键字),您需要调用 _process.next().

您可以认为多个 yield 是单个函数内 javascript 调试器中的 断点。直到 你告诉导航下一个断点它不会执行代码 堵塞。 (注意:不阻塞整个应用程序)

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

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

[rv] = yield [expression];

表达式:从生成器函数返回的值

yield any;
yield {age: 12};

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

您可以使用这种机制将参数传递给 process() 函数,以执行不同的 yield 部分。

let val = yield 99; 

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

Try It Now

用法

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

参考资料:

【讨论】:

    【解决方案3】:

    MDN documentation 相当不错,IMO。

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

    【讨论】:

    • @NicolasBarbulesco 如果您点击进入 MDN 文档,有一个非常明显的示例。
    • 这里引用 MDN 有什么意义?我想每个人都可以在 MDN 上阅读。访问davidwalsh.name/promises 了解更多信息。
    • 当(a)它是提问者所说的“非常糟糕的文档”的副本并且(b)它说没有任何帮助时,它是如何获得约 80 票的?下面有更好的答案。
    • 如果有人要求解释,只是复制粘贴文档是完全没有用的。提问意味着您已经在文档中搜索过,但您不理解它们。
    • MDN 文档是 JS 上最难理解的,当你只想知道它“做什么”时,会使用很多技术术语,仅此而已。
    【解决方案4】:

    简化/详细说明 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,所有这一切都可以在nice co-routine框架的帮助下一行完成。

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

    所以现在你的 main 函数将在需要等待变量和加载的东西时在必要的地方产生。但是现在,为了运行它,你需要调用一个normal(非协程函数)。一个简单的协程框架就可以解决这个问题,所以你只需要运行这个:

    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”。因此,您实际上可以通过简单地创建相同的函数签名(不带 cb)并返回 function (cb) {} 来将任何回调函数转换为使用 yield,如下所示:

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

    希望有了这些知识,您可以编写更简洁、更易读的代码easy to delete

    【讨论】:

    • a function* 只是一个没有yield的常规函数​​?
    • 我想你的意思是function * 是一个包含 yield 的函数。这是一个称为生成器的特殊函数。
    • 对于已经在任何地方都使用yield 的人,我确信这比回调更有意义,但我看不出这比回调更具可读性。
    • 那篇文章很难理解
    【解决方案5】:

    给出一个完整的答案: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】:

      它用于迭代器生成器。基本上,它允许您使用程序代码制作(可能是无限的)序列。见Mozilla's documentation

      【讨论】:

        【解决方案7】:

        javaScript 函数中的Yield 关键字使其成为生成器,

        什么是 JavaScript 中的生成器?

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

        含义生成器帮助我们与帮助迭代器异步工作,哦,现在黑客迭代器是什么?真的吗?

        迭代器意味着我们可以一次访问一个项目

        从哪里迭代器帮助我们一次访问一个项目? 它帮助我们通过生成器函数访问项目,生成器函数是我们使用yield关键字的那些,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 'What\'s 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; // "What's your age"
        
        _getMeDrink.next('20').value; // "ok you are eligible for it"
        
        _getMeDrink.next().value; // undefined
        
        

        让我简单解释一下发生了什么

        您注意到执行在每个 yield 关键字处暂停,我们能够在迭代器 .next() 的帮助下访问第一个 yield

        这一次迭代所有yield关键字,然后在没有更多yield关键字时返回未定义,用简单的话你可以说yield关键字是函数每次暂停并仅恢复的断点在我们的案例中使用迭代器调用它时:_getMeDrink.next() 这是帮助我们访问函数中每个断点的迭代器示例。

        生成器示例: async/await

        如果您看到async/await 的实现,您将看到generator functions &amp; promises 用于使async/await 工作,请指出任何建议欢迎。

        【讨论】:

        • 最具教育意义的回答!! ???
        【解决方案8】:

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

        【讨论】:

          【解决方案9】:

          使用 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 
          

          【讨论】:

            【解决方案10】:

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

            另一个如何使用 yield 的好例子。

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

            【讨论】:

            • 美丽的例子,正是我想要理解的。如果没有 request() 函数,我不清楚是谁在用值调用 it.next()。
            【解决方案11】:

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

            【讨论】:

              【解决方案12】:

              一个简单的例子:

              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}
              

              【讨论】:

                【解决方案13】:

                我也在尝试理解 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 对两者都有帮助。

                在没有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

                查询4 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

                查询4 0

                【讨论】:

                  【解决方案14】:

                  不要忘记非常有用的“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);
                  }
                  

                  【讨论】:

                  • 那是因为我认为生成器是一个迭代器。
                  猜你喜欢
                  • 2014-09-27
                  • 2022-11-29
                  • 2020-01-22
                  • 2023-03-12
                  • 2021-09-20
                  • 2011-05-18
                  • 2017-05-07
                  相关资源
                  最近更新 更多