JavaScript中的yield关键字是什么?

What's the yield keyword in JavaScript?

我在javascript中听说过一个"yield"关键字,但我发现它的文档非常糟糕。有人能给我解释(或推荐一个解释)它的用法和用途吗?


回答晚了,现在可能大家都知道yield,但也有一些更好的文档。

将James Long的"javascript的未来:生成器"中的一个例子改编为官方的和谐标准:

1
2
3
4
5
6
function * foo(x) {
    while (true) {
        x = x * 2;
        yield x;
    }
}

"When you call foo, you get back a Generator object which has a next
method."

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

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


国际海事组织,MDN文件很好。

The function containing the yield keyword is a generator. When you call it, its formal parameters are bound to actual arguments, but its body isn't actually evaluated. Instead, a generator-iterator is returned. Each call to the generator-iterator's next() method performs another pass through the iterative algorithm. Each step's value is the value specified by the yield keyword. Think of yield as the generator-iterator version of return, indicating the boundary between each iteration of the algorithm. Each time you call next(), the generator code resumes from the statement following the yield.


简化/详细描述尼克·索提罗斯的回答(我认为这很好),我认为最好描述一下如何开始用yield编码。

在我看来,使用yield的最大好处是它将消除我们在代码中看到的所有嵌套回调问题。一开始很难理解,这就是为什么我决定写这个答案(为我自己,希望是其他人!)

它的方法是引入一个协同程序的概念,这是一个可以自动停止/暂停直到它得到它所需要的功能。在javascript中,它由function*表示。只有function*函数才能使用yield

下面是一些典型的javascript:

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

这是笨拙的,因为现在所有的代码(显然需要等待这个loadFromDB调用)都需要在这个丑陋的回调中。这是不好的,有几个原因…

  • 所有代码缩进一级
  • 你有这样一个目的,你需要追踪到每一个地方。
  • 所有这些额外的function (err, result)行话
  • 不完全清楚你这样做是为了给result赋值。

另一方面,使用yield,所有这些都可以在nice共同例程框架的帮助下在一行中完成。

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

所以现在,当需要等待变量和加载内容时,您的主函数将在必要时生成。但是现在,为了运行这个,您需要调用一个正常的(非协程函数)。一个简单的协同程序框架可以解决这个问题,这样您所要做的就是运行这个:

1
start(main())

开始被定义(从尼克·索蒂罗的回答)

1
2
3
4
5
6
7
8
9
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实际上只是一个关键字,您可以将它放在带有回调的函数之前。

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

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

1
2
3
4
5
function yieldAsyncFunc(arg1, arg2) {
  return function (cb) {
    realAsyncFunc(arg1, arg2, cb)
  }
}

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


很简单,就是这样

  • yield关键字只帮助在任何时候异步暂停和恢复函数。
  • 此外,它还有助于从生成器函数返回值。

以这个简单的生成器函数为例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
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');
}

let _process = process();

let out1 = _process.next();
console.log(out1);

let out2 = _process.next();
console.log(out2);

let out3 = _process.next(90);
console.log(out3);

let _process = process();

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

You can think multiple yields are the breakpoints in a javascript debugger within a single function. Until
you tell to navigate next breakpoint it wont execute the code
block. (Note: without blocking the whole application)

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

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

1
[rv] = yield [expression];

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

1
2
yield any;
yield {age: 12};

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

Simply you can pass parameters to process() function with this mechanism, to execute different yield parts.

1
2
3
4
let val = yield 99;

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

现在试试看

惯例用法

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

参考文献:

  • https://developer.mozilla.org/en-us/docs/web/javascript/reference/operators/yield
  • http://javascript.tutorialhorizon.com/2015/09/16/generators-and-yield-in-es6/
  • https://strongloop.com/strongblog/how-to-generators-node-js-yield-use-cases/

给出一个完整的答案:yield的工作原理与return相似,但在发电机中。

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

1
2
3
4
5
6
7
8
9
10
11
12
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关键字还有另一个用途。它可用于向生成器发送值。

为了澄清,举个小例子:

1
2
3
4
5
6
7
8
9
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)发送给发电机。

这使我们能够找到一些真正有趣的东西。(查看连体衣)


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


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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
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());

使用yield关键字的斐波那契序列生成器。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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()