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

问题描述 投票:4回答:3

将参数传递给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并暂停收益率(以及返回nextfoo1调用作为参数42
  2. 暂停,直到下一次打电话给next
  3. 在下一个收益率继续与var x = 1 + 42一致,因为这是以前收到的论点
  4. 打印x = 43
  5. 只是从最后一个{done: true}返回一个next,忽略它的论点(43)并停止。

现在,显然,这不是正在发生的事情。那么......我在这里弄错了什么?

javascript generator yield
3个回答
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表达式的值是用这个调用中给出的参数的值计算/替换的(不是前一个给出的那个,如我所料),执行运行直到第二个yieldnext返回第二个yield的参数值 - 这是我的错误:我假设第一个yield表达式的值是第一次调用next的参数,但它实际上是第二个调用的参数对于next,或另一种方式来说,它是next调用的参数,在执行期间实际计算了该值

这可能对谁发明这个更有意义,因为对next的调用次数是yield语句数量的一倍(还有最后一个返回{ value: undefined, done: true }来表示终止),所以如果第一次调用的参数不是忽略,然后必须忽略最后一次调用之一。此外,在评估next的主体时,替换将从其先前调用的参数开始。这将是更加直观的imho,但我认为它是关于遵循其他语言的生成器的约定,并且一致性最终是最好的...


偏离主题但很有启发性:刚尝试在Python中进行相同的探索,显然实现了类似于Javascript的生成器,当我试图将参数传递给第一次调用TypeError: can't send non-None value to a just-started generator时,我立即得到了一个next()(清楚地表明我的心智模型是错误的!),迭代器API也通过抛出StopIteration异常结束,所以没有“额外”next()需要检查done是否为真(我想这个额外调用使用最后一个参数的副作用只会导致很难理解和调试代码......)。比JS更容易“理解它”......


2
投票

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

收到.next(arg)后,发电机的第一个动作是将arg送到yield。但是在第一次打电话给.next()时,没有yield接受这个,因为它只是在执行结束时。

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


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    
*/
© www.soinside.com 2019 - 2024. All rights reserved.