JavaScript中的yield关键字是什么?

304
我听说过 JavaScript 中的 "yield" 关键字。它有什么用途,我该如何使用它?

他可能是指“Yield”。http://bytes.com/topic/python/answers/685510-yield-keyword-usage - ant
6
这在MDN中有解释,但我认为这只适用于 Firefox,对吗?它的可移植性如何?是否有方法可以在 Chrome 或 Node.js 上实现这个功能?附注:抱歉,这是JavaScript v1.7+,所以在寻找支持时要查看该属性。 - Trylks
3
自从Node v0.11.2版本起,生成器已经可用。 - Janus Troelsen
1
@JanusTroelsen,然而,只有在标志后面。它们在ioJS中得到本地支持。 - Dan
1
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/yield - chharvey
1
注意:yield不受Internet Explorer支持。 - Krisztián Balla
15个回答

261

参考 James Long 的 "Javascript's Future: Generators" 一文中的示例,为官方 Harmony 标准进行适配:

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

"当你调用foo时,会返回一个生成器对象,该对象具有next方法。"
var g = foo(2);
g.next(); // -> 4
g.next(); // -> 8
g.next(); // -> 16

yield有点像return:你会得到一些回报。 return x返回x的值,但是yield x返回一个函数,它可以提供迭代到下一个值的方法。如果你有一个可能很占用内存的过程,并且你想在迭代期间中断它,这将非常有用。


16
有帮助的,但我猜你的意思是 function* foo(x){ - Rana Deep
9
函数语法被扩展以添加可选的 * 标记,是否需要它取决于您返回的未来类型。详细信息较长:GvR 在 Python 实现中解释了它,JavaScript 实现是基于 Python 的。使用 function * 总是正确的,但在某些情况下比具有 yieldfunction 多一些开销。 - bishop
1
@Ajedi32 是的,你说得对。Harmony标准化了function *yield之间的关联,并添加了引用错误(“如果在非生成器函数中出现yield或yield*表达式,则会引发早期错误”)。但是,Firefox中的原始Javascript 1.7实现不需要*。已相应更新答案。谢谢! - bishop
3
JavaScript终于成为了一门你实际能够使用的语言。这就是进化。 - Lukas Liesis
1
例子很有用,但是什么是函数*? - Don Diego
显示剩余2条评论

113

非常简单,它是如何工作的

  • 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();
在调用 _process.next() 之前,它不会执行代码的前两行,然后第一个 yield 将暂停函数。 要恢复函数直到下一个暂停点(yield 关键字),需要调用 _process.next()。
可以将多个 yields 视为单个函数中 javascript 调试器中的断点。在告诉它导航到下一个断点之前,它不会执行代码块。(注意:不会阻塞整个应用程序)
但是,虽然 yield 执行此暂停和恢复行为,但它也可以返回一些结果 {value: any, done: boolean}。 根据先前的函数,我们没有发出任何值。如果我们探索先前的输出,它将显示相同的 { value: undefined, done: false },其值为 undefined。
让我们深入了解yield关键字。您可以选择添加表达式并设置分配默认可选值。(官方文档语法)
[rv] = yield [expression];

expression:从生成器函数返回的值

yield any;
yield {age: 12};

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

通过此机制,您可以向process()函数传递参数以执行不同的yield部分。

let val = yield 99; 

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

立即尝试

用途

  • 惰性求值
  • 无限序列
  • 异步控制流

参考资料:


84

MDN 文档 在我看来相当不错。

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


3
如果您点击MDN文档,会有一个非常明显的示例。@NicolasBarbulesco - Matt Ball
5
引用 MDN 的目的是什么?我认为每个人都可以在 MDN 上阅读。访问 https://davidwalsh.name/promises 以了解更多信息。 - Ejaz Karim
59
这个回答为什么能获得约80个赞,因为它既是提问者所称的“非常差劲的文档”的复制品,又没有提供任何有用信息?下面的回答要好得多。 - www-0av-Com
14
如果有人要求解释,仅仅复制粘贴一份文件是完全没有用的。请求意味着你已经在文档中搜索过了,但你并没有理解它们。 - Don Diego
5
MDN文档中关于JS的部分非常晦涩难懂,使用了大量技术术语,而当你只是想知道它“做什么”的时候,这些术语并没有起到帮助作用。 - dawn
显示剩余3条评论

59

对 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,借助于优美的协程框架,所有这些工作可以在一行代码内完成

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

因此,现在您的主函数在需要等待变量和其他内容加载时会产生(yield)结果。但是,为了运行它,您需要调用一个普通(非协程函数)。一个简单的协程框架可以解决这个问题,让您只需运行以下代码:

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,如下所示:

会打印出"Hello World"。所以你可以通过创建与回调函数相同的函数签名(没有cb),并返回function(cb){}来轻松地将任何回调函数转换为使用yield

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

希望通过这个知识,您可以编写更干净、更易读的代码,这样就可以 轻松删除代码


function*只是一个普通函数,没有yield的话就和普通函数一样。 - Honinbo Shusaku
我认为你的意思是 function * 是一个包含 yield 的函数。它是一种特殊的函数,称为生成器。 - Leander
14
对于已经在使用 yield 的人来说,我相信这比回调函数更容易理解,但我并不认为这比回调函数更易读。 - palswim
3
那篇文章很难理解。 - MartianMartian
1
到目前为止,回调函数看起来比yield更简单直观。 - Sushil

25

yield 关键字可以将 JavaScript 函数变为生成器函数。

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关键字处暂停,我们能够借助迭代器.next()访问第一个yield

这逐个迭代所有yield关键字,当没有更多的yield关键字时返回undefined。简单地说,yield关键字是函数暂停的断点,只有在使用迭代器调用它时才会恢复,对于我们的情况:_getMeDrink.next()这是一个帮助我们访问函数中每个断点的迭代器示例。

生成器的示例:async/await

如果您查看 async/await 的实现,您将看到使用生成器函数和承诺使async/await工作,请指出任何建议均受欢迎。


5
最富教育意义的答案!! - CicheR

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,通过将其发送到生成器,在它停在第一个yield(返回0)之后。

这使我们能够做一些非常时髦的事情。(查找协程)


17

这个功能主要用于迭代器生成器。基本上,它允许你使用过程式代码创建一个(可能是无限的)序列。请参见Mozilla文档


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

5
使用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 

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


漂亮的例子,正是我想要理解的。如果没有request()函数,我不清楚是谁调用了it.next()并传递了一个值。 - SijuMathew

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接