【问题标题】:yield * with generator function without yieldyield * 带生成器函数,不带 yield
【发布时间】:2021-09-05 09:45:52
【问题描述】:

以下是我对 yield * 运算符的确切机制的理解,而不是 yield 在阅读文档并使用它后想到它:

在生成器函数返回的迭代器上调用next()时,如果遇到:

  • yield someFunction() :它应该调用someFunction() 并返回一个pair 对象,其返回值在value 中,以及是否有更多指令进入done
  • yield * someGenerator() :它应该使用someGenerator() 返回的迭代器,在其上调用next() 函数并返回一个pair 对象,其中value 从迭代器获得,done 只到true如果两个迭代器都返回了 done===true 并且没有更多的指令来了

换句话说,yield * 将迭代器的下一步委托给另一个生成器函数。 因此我希望someFunction() 只是一个普通的生成器,即使它没有yield 语句,但只有return,它也会暂停其执行(以及它的调用者之一)声明甚至根本没有return 声明。

但好像不是这样的。

看看这个例子,我们在游戏流程中使用生成器,目标是每次我们yield,我们可以暂停执行以将新的游戏状态发送到客户端,例如。所以mainGameFlow 生成器将像函数调用一样委托给其他生成器,但我们希望在每一步之间暂停执行:

function* mainGameFlow() {
  console.log('   (mainGameFlow) Will give money')
  yield* giveMoney()
  console.log('   (mainGameFlow) Will give card')
  yield* giveCard()
}

function* giveMoney() {
  console.log('      (giveMoney) Giving money to player')
}

function* giveCard() {
  console.log('      (giveCard) Giving card to player')
  // if(card has effect)...
  console.log('      (giveCard) Will apply card\'s effects')
  yield* applyCardEffect()
}

function* applyCardEffect() {
  console.log('         (applyCardEffect) Applying card effect')
}

console.log('Will get iterator from generator')
const iterator = mainGameFlow()
console.log('Will launch iterator first step')
iterator.next()
console.log('Iterator paused')

我希望在mainGameFlow 迭代器上对next() 的第一次调用会在记录“给玩家钱”之后暂停其执行。因为当一个生成器刚刚返回时,它就像它产生时一样停止它的流动。 但在这里,所有的日志记录行都到达了,主迭代器只有在整个流程发生后才暂停。

我的问题是:你认为我的代码有问题吗?如果没有,您是否知道比 yield * 上的 MDN 更好的文档,可以清楚地理解为什么在这个用例中流程会继续?

【问题讨论】:

  • 生成器函数在对第一个 yield 或 return 语句的第一个 next() 调用上运行。正如您所说,“yield* 将迭代器的下一步委托给另一个生成器函数。”
  • 如果您将 yield 和 return 视为生产者,将 .next() 视为消费者,这可能会更容易理解。每次消费都恰好匹配一种生产。
  • "someFunction() 只是一个普通的生成器" - 你的意思是someGenerator()

标签: javascript return generator yield


【解决方案1】:

MDN 的这一部分很好地解释了它(阅读第三点):

一旦在 yield 表达式上暂停,生成器的代码执行 保持暂停,直到调用生成器的 next() 方法。每个 当调用生成器的 next() 方法时,生成器恢复 执行,并一直运行,直到达到以下条件之一:

  • yield,导致生成器再次暂停并返回生成器的新值。下次调用 next() 时,执行
    在 yield 之后立即恢复语句。
  • throw 用于从生成器中抛出异常。这会完全停止生成器的执行,并在
    调用者(通常是抛出异常时的情况)。
  • 已到达生成器函数的末尾。在这种情况下,生成器的执行结束并返回一个 IteratorResult 给调用者,其中值未定义且 done 为真。
  • 到达返回语句。在这种情况下,生成器的执行结束并且 IteratorResult 返回给调用者
    其中值是return语句指定的值,
    done 是真的。

yield * 表达式与生成器函数或迭代器一起使用。 如果 iterable 为空或生成器函数没有 yield 关键字,则从主生成器函数调用返回的迭代器将在第一次调用 next() 时完成,并且不会在 yield * 调用之间暂停。

function* parentGenerator(){
  console.log("parentGenerator:: start");
  yield* childGeneratorOne();
  yield* childGeneratorTwo();
  console.log("parentGenerator:: end");
}

function* childGeneratorOne(){
  //does not yield a value, so it is complete
  console.log("childGeneratorOne:: start");
}

function* childGeneratorTwo(){
  //does not yield a value, so it is complete
  console.log("childGeneratorTwo:: start");
}

const itr = parentGenerator();
console.log(itr.next());

因此,在您的情况下,当您调用 mainGameFlow() 生成器函数时,控件会在没有 yield 关键字的 yield * 表达式的帮助下转到 giveMoney() 生成器函数。

它返回一个IteratorResult,其中valueundefineddonetrue,因此永远不会暂停并继续执行下一行。

giveCard() 调用也会发生同样的情况,它还返回一个 undefined 作为 valuedonetrueIteratorResult,因此函数永远不会暂停并运行到完成,因为在所有情况下,done 都是真的。

您可以通过在 giveMoney() 生成器函数中放置 yield 1 来测试此理论。主生成器函数将暂停,直到您再次调用next(),这是因为giveMoney() 的结果是{ value: 1, done: false},它会暂停直到donetrue

function* mainGameFlow() {
  console.log('   (mainGameFlow) Will give money')
  yield* giveMoney()
  console.log('   (mainGameFlow) Will give card')
  yield* giveCard()
}

function* giveMoney() {
  console.log('      (giveMoney) Giving money to player')
  //paused here until next() is called again
  yield 1;
}

function* giveCard() {
  console.log('      (giveCard) Giving card to player')
  console.log('      (giveCard) Will apply card\'s effects')
  yield* applyCardEffect()
}

function* applyCardEffect() {
  console.log('         (applyCardEffect) Applying card effect')
}

console.log('Will get iterator from generator')
const iterator = mainGameFlow()
console.log('Will launch iterator first step')
iterator.next()
console.log('Iterator paused')

【讨论】:

    【解决方案2】:

    我希望在 mainGameFlow 迭代器上对 next() 的第一次调用会在记录“给玩家钱”之后暂停其执行。因为当生成器刚返回时,它会像在 yield 时一样停止流动

    是的 - 遇到return 语句的giveMoney() 生成器确实 停止。但是外部生成器iterator 仍然需要产生一个值——并且由于giveMoney() 已经完成,yield* 语句继续执行mainGameFlow 代码,一直运行直到它遇到下一个yield 语句.

    【讨论】:

      猜你喜欢
      • 2017-08-29
      • 2020-04-07
      • 2011-11-02
      • 1970-01-01
      • 2017-07-07
      • 2017-07-07
      • 1970-01-01
      • 2016-04-10
      • 2020-05-25
      相关资源
      最近更新 更多