277

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

4

14 回答 14

243

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

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

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

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

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

yield有点像return:你得到了一些东西 。return x返回 的值x,但yield x返回一个函数,该函数为您提供了一种迭代下一个值的方法。如果您有一个可能需要在迭代期间中断的潜在内存密集型过程,则很有用。

于 2013-12-31T15:58:02.687 回答
91

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

  • 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.next()之前,它不会执行前 2 行代码,然后第一个 yield暂停该函数。要恢复函数直到下一个暂停点(yield 关键字),您需要调用_process.next()

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

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

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

[rv] = yield [expression];

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

yield any;
yield {age: 12};

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

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

let val = yield 99; 

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

现在就试试

用法

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

参考:

于 2017-01-31T14:31:29.043 回答
83

IMO的MDN 文档非常好。

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

于 2010-02-17T15:59:00.757 回答
57

简化/详细说明 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 协程框架的帮助下在一条线上完成。

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

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

start(main())

并定义了开始(来自尼克索蒂罗的回答)

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

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

于 2016-03-16T09:11:30.877 回答
19

给出一个完整的答案: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)之后。

这使我们能够获得一些非常时髦的东西。(查找协程)

于 2014-09-14T18:36:03.267 回答
17

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

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关键字处暂停,我们可以yield在迭代器的帮助下首先访问.next()

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

生成器示例: async/await

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

于 2020-02-07T16:31:34.923 回答
16

它用于迭代器生成器。基本上,它允许您使用程序代码制作(可能是无限的)序列。请参阅Mozilla 的文档

于 2010-02-17T15:58:44.937 回答
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());
于 2014-11-04T17:33:11.127 回答
5

使用 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 
于 2018-03-21T21:59:12.467 回答
3

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

于 2019-03-06T12:52:51.833 回答
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}
于 2019-12-03T13:24:31.397 回答
0

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

于 2019-09-16T09:30:44.007 回答
0

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

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

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

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

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

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

异步查询的难点在于:

  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

于 2020-01-08T02:15:10.627 回答
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);
}
于 2020-01-26T10:05:41.227 回答