ES6生成器机制 - 传递给next()的第一个值在哪里?

时间:2017-05-29 16:11:03

标签: javascript generator yield

将参数传递给ES6生成器的next()时,为什么忽略第一个值?更具体地说,为什么输出结果为x = 44而不是x = 43

function* foo() {
    let i = 0;
    var x = 1 + (yield "foo" + (++i));
    console.log(`x = ${x}`);
}

fooer = foo();

console.log(fooer.next(42));
console.log(fooer.next(43));

// output:
// { value: 'foo1', done: false }
// x = 44
// { value: undefined, done: true }

我对这种发电机行为的心理模型如下:

  1. 返回foo1并暂停收益(以next调用返回foo1作为参数42
  2. 暂停,直到下次拨打next
  3. 在下一次收益时进入var x = 1 + 42的行,因为这是以前收到的参数
  4. print x = 43
  5. 只返回上一个{done: true}的{​​{1}},忽略其参数(next)并停止。
  6. 现在,显然,这不是正在发生的事情。那么...... 我在这里出错了什么?

4 个答案:

答案 0 :(得分:4)

我最终编写了这种代码来更彻底地调查行为(重新阅读......后阅读MDN docs on generators):

function* bar() {
    pp('in bar');
    console.log(`1. ${yield 100}`);
    console.log(`after 1`);
    console.log(`2. ${yield 200}`);
    console.log(`after 2`);
}
let barer = bar();
pp(`1. next:`, barer.next(1));
pp(`--- done with 1 next(1)\n`);
pp(`2. next:`, barer.next(2));
pp(`--- done with 2 next(2)\n`);
pp(`3. next:`, barer.next(3));
pp(`--- done with 3 next(3)\n`);

输出:

in bar
1. next: { value: 100, done: false }
--- done with 1 next(1)

1. 2
after 1
2. next: { value: 200, done: false }
--- done with 2 next(2)

2. 3
after 2
3. next: { value: undefined, done: true }
--- done with 3 next(3)

所以显然正确的心理模型是这样的:

  • 在第一次调用next时,生成器函数体运行到yield表达式,"参数" yield(第一次100)作为next返回的值返回,生成器正文暂停,然后评估值收益率表达 - "之前" 部分至关重要

  • 仅在第二次调用next时,第一个 yield表达式的值被计算/替换为下一个给出的参数值< strong>此调用(不是我预期的上一个中给出的那个),并且执行一直运行到第二个yield,并且next返回第二次收益的论证的价值 - 这是我的错误:我认为第一个yield表达式的值是的参数第一次拨打next ,但它实际上是第二次拨打next 的参数,或者另外一种方式来表示它,#&#> 39; s next执行期间调用该值实际计算的参数

这可能对发明这一点的人更有意义,因为next的调用数量是yield语句的数量的一倍(还有最后一个{ value: undefined, done: true } TypeError: can't send non-None value to a just-started generator如果第一次调用的参数不会被忽略,那么最后一次调用就不得不被忽略了。此外,在评估next的主体时,替换将从其之前的调用的参数开始。这个会更加直观imho ,但我认为它也是关于遵循其他语言中的生成器的约定而且一致性最终是最好的...

偏离主题但很有启发性:刚尝试在Python中进行相同的探索,这显然实现了类似于Javascript的生成器,我在尝试传递参数时立即得到next()第一次调用StopIteration(明确表示我的心理模型错误!),迭代器API也以抛出next()异常结束,所以没有&#34;额外&#34; done只需要检查NULL是否为真(我想使用这个额外的调用来利用上一个下一个参数的副作用只会导致非常难以理解调试代码...)。更容易&#34; grok it&#34;而不是JS ...

答案 1 :(得分:2)

我是从Axel Rauschmayer's Exploring ES6得到的,特别是22.4.1.1。

收到.next(arg)后,生成器的第一个操作是将arg提供给yield。但是在第一次调用.next()时,没有yield来接收它,因为它只是在执行结束时。

仅在第二次调用x = 1 + 43被执行并随后记录,并且生成器结束。

答案 2 :(得分:0)

也很难将我的头缠绕在发电机周围,特别是当投入if语句取决于产生的值时。然而,if语句实际上是帮助我最终获得它的原因:

function* foo() {
  const firstYield = yield 1
  console.log('foo', firstYield)

  const secondYield = yield 3
  console.log('foo', secondYield)

  if (firstYield === 2) {
    yield 5
  }
}

const generator = foo()

console.log('next', generator.next( /* Input skipped */ ).value)
console.log('next', generator.next(2).value)
console.log('next', generator.next(4).value)

/* 
  Outputs:

  next 1
  foo 2
  next 3
  foo 4
  next 5    
*/

答案 3 :(得分:0)

一旦我意识到这一点,一切都会变得清晰起来。

这是您的典型生成器:

function* f() {
  let a = yield 1;
  // a === 200
  let b = yield 2;
  // b === 300
}

let gen = f();
gen.next(100) // === { value: 1, done: false }
gen.next(200) // === { value: 2, done: false }
gen.next(300) // === { value: undefined, done: true }

但这是实际发生的情况。 使生成器执行任何操作的唯一方法是在其上调用next()因此,生成器需要有一种方法来执行在第一个yield之前的代码。

function* f() {
  // All generators implicitly start with that line
  // v--------<---< 100
       = yield
  // ^-------- your first next call jumps right here

  let a = yield 1;
  // a === 200
  let b = yield 2;
  // b === 300
}