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


当前回答

我还试图理解yield关键字。根据我目前的理解,在生成器中,yield关键字就像CPU上下文开关一样。当yield语句运行时,将保存所有状态(例如局部变量)。

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

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

上次执行时保存的上下文。 这个执行的触发器注入的值。

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

异步查询的困难在于:

回调地狱 除非在回调中将它们作为参数传递,否则将丢失上下文。

产量和发电机可以帮助这两个。

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

其他回答

我还试图理解yield关键字。根据我目前的理解,在生成器中,yield关键字就像CPU上下文开关一样。当yield语句运行时,将保存所有状态(例如局部变量)。

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

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

上次执行时保存的上下文。 这个执行的触发器注入的值。

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

异步查询的困难在于:

回调地狱 除非在回调中将它们作为参数传递,否则将丢失上下文。

产量和发电机可以帮助这两个。

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

给出一个完整的答案: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关键字在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的实现,你会看到生成器函数和承诺被用来使async/await工作,请指出任何建议,欢迎。

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

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

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

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

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

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

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

所有代码都缩进了一层 你有这一端}),你需要随时跟踪 所有这些额外的功能(错误,结果)术语 不太清楚你这样做是为了给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)并返回函数(cb){}来将任何回调函数转换为使用yield,如下所示:

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

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