Javascript生成器:了解它们

时间:2013-12-25 03:59:51

标签: javascript node.js generator yield

我很确定我对发电机的理解本质上已被打破。所有在线资源似乎都存在冲突,这会带来令人难以置信的困难和令人困惑的学习体验。

据我所知,yield关键字使当前正在执行的代码块能够等待值,而不是抛出剩余的代码在回调中执行。因此,正如大多数教程所指出的那样,你可以使用它:

(function *() {
     // Wait until users have be got and put into value of `results`
     var results = yield db.get("users");
     // And continue
     view.display(results);
})();

而不是:

db.get("user", function(results) {
    view.display(results);
});

是的,在我尝试编写自己的发电机之前,这一切都很好。我遇到了几个问题:

  • 上面的第一个示例代码将不会运行,因为没有任何内容可以迭代生成器,对吗?有些人需要在某处调用.next,对吧?
  • 整个API必须重写到I / O调用以支持生成器,对吗?
  • 从我收集的内容来看,yield似乎代表等待值最常见的用例,而在实现部分中(读取:返回值为/ db.getyield似乎代表将此值发送回当前正在等待的块以恢复执行

以例如:

function *fn() {
    yield 1;
    yield "a";
}

var gen = fn();
gen.next(); // 1
gen.next(); // "a";
该上下文中的

yield正在向下发送值而不是等待结果。在上面的第一个示例中,它等待db.get和恢复执行的结果,而不是“返回”或发回一个值。如果db.get情况属实,那么这本身并不是同步的吗?我的意思是,它不完全相同:

(function() {
     //Wait for the results
    var results = fs.readFileSync("users.txt");
    // Use results
    view.display(results);
})();

不幸的是,如果从这个问题中明确表示(可能唯一清楚的是)我不了解发电机。希望我能在这里得到一些见解。

3 个答案:

答案 0 :(得分:21)

TL; DR:生成器的本质是控制代码执行的暂停。

对于生成器本身,您可以参考this

总而言之,您应该区分三个组件: 发电机功能 发电机 3.生成结果

生成器函数只是function,其头部为星形,而且(可选)yield位于其正文中。

function *generator() {
  console.log('Start!');
  var i = 0;
  while (true) {
    if (i < 3)
      yield i++;
  }
}

var gen = generator();
// nothing happens here!!

生成器函数本身不执行任何操作,但返回生成器(在上面的情况下为gen)。 此处没有控制台输出,因为只有在返回生成器next方法后,才会运行生成器函数的主体。 Generator有几种方法,其中最重要的方法是nextnext运行代码并返回生成器结果

var ret = gen.next();
// Start!
console.log(ret);
// {value: 0, done: false}
上面的

ret生成器结果。它有两个属性:value,您在生成器函数中产生的值,以及done,一个标志,指示生成器函数是否返回。 / p>

console.log(gen.next());
// {value: 1, done: false}
console.log(gen.next());
// {value: 2, done: false}
console.log(gen.next());
// {value: undefined, done: true}

此时,没有人会指望你理解发电机,至少不是发电机的异步功率。

简单来说,生成器有两个功能:

  • 可以选择跳出函数并让外部代码确定何时跳回函数。
  • 异步调用的控制可以在代码之外完成

在代码中,yield跳转到函数之外,next(val)跳回函数并将值传递回函数。 外部代码可以处理异步调用并决定切换到您自己的代码的适当时间。

再次查看样本:

var gen = generator();
console.log('generated generator');
console.log(gen.next().value);
// mock long long processing
setTimeout(function() {
  console.log(gen.next().value);
  console.log('Execute after timer fire');
}, 1000);
console.log('Execute after timer set');

/* result:
    generated generator
    start
    0
    Execute after timer set
    1
    Execute after timer fire
*/

请参阅?生成器函数本身不处理回调。外部代码可以。

基地就在这里。您可以详细说明此代码以支持完全异步,同时保持生成器功能,例如同步。

例如,假设geturl是一个返回promise对象的异步调用。你可以写var html = yield getUrl('www.stackoverflow.com');这跳过你的代码。外部代码将执行以下操作:

var ret = gen.next();
ret.then(function (fetchedHTML) {
  // jumps back to your generator function
  // and assign fetchHTML to html in your code
  gen.next(fetchedHTML);
});

有关更完整的指南,请参阅this。 以及cogalaxysuspend

等存储库

答案 1 :(得分:3)

如果是生成器的一部分,

没有异步的东西。生成器只是暂停和恢复代码块。当你使用我称之为https://github.com/visionmedia/co的“生成器引擎”时,会发生所有异步魔法。

基本上,gen.next()所做的是返回最后yield个ed值,并允许您在yield被分配给某个内容时返回一个值,例如。 var something = yield 1。所以如果你有代码块:

function* genfun() {
  var a = yield 1
  var b = yield 2
}

var gen = genfun()

gen.next() // returns the first yielded value via `{value: 1}`
gen.next(1) // sets `a` as 1, returns the next yielded value via `{value: 2}`
gen.next(2) // sets `b` as 2, the generator is done, so it returns `{done: true}`

gen.throw(err)与next相同,除了抛出错误而不是分配给变量。

这就是控制流引擎的工作原理 - 你得到的下一个值可能是回调等等。执行回调,并且在回调完成之前不要gen.next()

答案 2 :(得分:1)

这两个例子不一样。当你屈服时,该函数现在实际上变成了一个回调,等待db.get(“users”)完成时执行。这样,该函数不会阻止其他函数的执行。将其视为将同步函数转换为异步函数的一种方法,让系统知道您可以在某些点暂停。