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


当前回答

给出一个完整的答案: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)后将其发送给生成器。

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

其他回答

简化/详细阐述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)
  }
}

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

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

我还试图理解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

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

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

函数请求(url) { Axios.get (url).then((response) => { it.next(响应); }) } 函数* main() { Const result1 = yield request('http://some.api.com'); Const result2 = yield request('http://some.otherapi?Id =' + result1。id); console.log('您的响应是:' + result2.value); } Var it = main(); it.next ()

给出一个完整的答案: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)后将其发送给生成器。

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