什么是yield关键字在JavaScript?

我听说过JavaScript中的“yield”关键字,但我发现关于它的文档非常糟糕。有人能给我解释一下(或者推荐一个解释它的网站)它的用法和用途吗?

163738 次浏览

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

在我看来,MDN文档很不错。

包含yield关键字的函数是一个生成器。当你调用它时,它的形式形参绑定到实际的实参,但它的主体并不实际计算。相反,返回一个生成器-迭代器。对生成器-迭代器的next()方法的每次调用都执行迭代算法的另一次传递。每个步骤的值都是由yield关键字指定的值。把yield看作是return的生成迭代器版本,表示算法每次迭代之间的边界。每次调用next()时,生成器代码都从yield后面的语句开始。

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

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

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

"当你调用foo时,你得到一个Generator对象,它有一个next 方法。" < / p >

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

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

给出一个完整的答案: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时,在它停止在第一个yield(返回0)后将其发送给生成器。

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

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

简化/详细阐述Nick Sotiros的答案(我认为非常棒),我认为最好描述如何开始使用yield编码。

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

它的方法是通过引入协程的思想,这是一个可以自动停止/暂停的函数,直到它得到它需要的东西。在javascript中,用function*表示。只有function*函数可以使用yield

下面是一些典型的javascript代码:

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

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

  • 所有代码都缩进了一层
  • 你有这个end }),你需要到处跟踪它
  • 所有这些额外的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”。因此,你实际上可以通过简单地创建相同的函数签名(不带cb)并返回function (cb) {}来将任何回调函数转换为使用yield,如下所示:

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

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

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

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

Let _process = process();

直到你调用< em > _process.next () < / em >不会执行前两行的代码,然后第一次产生暂停函数。 为了重新开始函数直到next 暂停 point (生成的关键字),你需要调用< em > _process.next () < / em >.

. 0
你可以认为多个收益率是一个函数中javascript调试器中的断点。直到 你告诉导航下一个断点,它不会执行代码 块。(请注意:不阻塞整个应用程序)

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

让我们深入研究yield关键字。你可以选择添加表达式并设置指定一个默认的可选值。(官方文档语法)

[rv] = yield [expression];

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

yield any;
yield {age: 12};

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

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

let val = yield 99;


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

Now Try It Now .

< / em > < em >用法

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

引用:

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

function* fibonacci() {
var a = -1, b = 1, c;
while(1) {
c = a + b;
a = b;
b = c;
yield c;
}
}


var fibonacciGenerator = fibonacci();
fibonacciGenerator.next().value; // 0
fibonacciGenerator.next().value; // 1
fibonacciGenerator.next().value; // 1
fibonacciGenerator.next().value; // 2

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

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

举个简单的例子:

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}

我还试图理解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+++++++++++");

运行结果如下:

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

query1 0

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

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

运行结果如下:

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

query1 0

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

query2 1

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

Yield关键字在javaScript函数使其生成器,

什么是JavaScript生成器?

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

这意味着生成器可以帮助我们与帮助迭代器异步工作,哦,hack迭代器是什么?真的吗?

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

从哪里迭代器帮助我们一次访问一项? 它帮助我们通过生成器函数访问项目,生成器函数中我们使用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关键字时返回undefined,简单地说,你可以说yield关键字是断点,函数每次暂停,只有在使用迭代器调用它时才恢复,对于我们的例子:_getMeDrink.next()这是一个迭代器的例子,它帮助我们访问函数中的每个断点。

生成器示例: async/await < / p >

如果你看到async/await的实现,你会看到generator functions & promises 被用来使async/await工作,请指出任何建议都是欢迎的。