是否可以将ECMAScript 6发生器重置为其初始状态?

时间:2014-05-24 17:59:02

标签: javascript generator ecmascript-harmony ecmascript-6

鉴于提供的(非常简单的)生成器,是否可以将生成器恢复到其原始状态以便再次使用?

var generator = function*() {
    yield 1;
    yield 2;
    yield 3;
};

var iterable = generator();

for (let x of iterable) {
    console.log(x);
}

// At this point, iterable is consumed.
// Is there a method for moving iterable back
// to the start point by only without re-calling generator(),
// (or possibly by re-calling generator(), only by using prototype 
//  or constructor methods available within the iterable object)
// so the following code would work again?

for (let x of iterable) {
    console.log(x);
}

我希望能够将iterable传递给其他一些范围,迭代它,做一些其他的东西,然后能够在稍后的同一范围内再次迭代它。

11 个答案:

答案 0 :(得分:16)

如果你的意图是

  

到其他一些范围,迭代它,做一些其他的东西,然后能够在稍后的同一范围内再次迭代它。

那么你唯一不应该尝试做的就是传递迭代器,而不是传递生成器:

var generator = function*() {
    yield 1;
    yield 2;
    yield 3;
};

var user = function(generator){

    for (let x of generator()) {
        console.log(x);
    }

    for (let x of generator()) {
        console.log(x);
    }
}

或者只是制作一个“循环”迭代器并在迭代时检查

var generator = function*() {
    while(true){
        yield 1;
        yield 2;
        yield 3;
    }
};

for( x in i ){
    console.log(x);
    if(x === 3){
        break;
    }
}

答案 1 :(得分:13)

  

此时,将消耗iterable。

这意味着其内部[[GeneratorState]]为completed

  

是否有一种方法可以通过重新调用generator()

将iterable移回起始点

没有。规范说明

  

一旦生成器进入“已完成”状态,它就永远不会离开它,并且永远不会恢复其关联的执行上下文。此时与生成器关联的任何执行状态都可以丢弃。

  

或者可能只通过使用原型重新调用generator()    或者可迭代对象中可用的构造方法

没有。虽然未在规范中明确说明,但iterable object上没有比[[GeneratorState]]和[[GeneratorContext]]更多的特定于实例的属性。

然而,提供信息的"Generator Object Relationships" grapic州:

  

每个生成器函数都有一个没有构造函数属性的关联原型。因此,生成器实例不会公开对其生成器函数的访问。

  

我希望能够将iterable传递给其他范围

改为通过发电机功能。或者产生新生成器实例的东西。

答案 2 :(得分:4)

尽我所知,我认为这是不可能的。每个this useful wikidraft version of ES6生成器,一旦您从它返回(而不是屈服),它会将其置于"closed"状态,并且无法移动它回到"newborn"状态,这是新发电机的开始状态。

您可能必须将回调传递给其他范围以创建新的生成器。作为一种解决方法,您甚至可以将该回调作为自定义方法添加到您发送到其他范围的生成器(如果需要),并且该回调将为另一个范围创建新生成器。

如果您考虑生成器如何工作,他们必须从头开始执行以重置其初始状态,并且根本没有理由支持它。对于为什么你不能在现有对象上重新执行构造函数并期望在同一对象中有一个处女对象,这将是一个类似的问题。虽然它在技术上都是可行的,但是做正确的工作很麻烦,并且没有理由支持它。如果你想要一个原始对象,只需创建一个新对象。与发电机相同。


这有点像黑客,但需要考虑好奇的事情。你可以制造一个重复自己的发电机。假设您的发电机工作如下:

var generator = function*() {
    while (true) {
        yield 1;
        yield 2;
        yield 3;
        yield null;
    }
};

var iterable = generator();

for (let x of iterable) {
    if (x === null) break;
    console.log(x);
}

// generator is now in a state ready to repeat again

我很容易看出这可能是一种反模式,但是如果你这样做的话:

for (let x of iterable) {
    console.log(x);
}

你将拥有一个无限循环,因此必须非常谨慎地使用它。仅供参考,上面的维基显示了无限斐波纳契数列的例子,因此当然可以考虑使用无限生成器。

答案 3 :(得分:4)

根据draft version of ES6

  

一旦生成器进入"completed"状态,它就永远不会离开它,并且永远不会恢复其关联的执行上下文。此时与生成器关联的任何执行状态都可以丢弃。

因此,一旦完成,就无法重置它。这样做也是有道理的。我们称之为发电机,原因是:)

答案 4 :(得分:3)

你也可以让你的生成器像这样重置你的迭代:

let iterable = generator();

function* generator(){
    yield 1;
    yield 2;
    yield 3;
    iterable = generator();
}

for (let x of iterable) {
    console.log(x);
}

//Now the generator has reset the iterable and the iterable is ready to go again.

for (let x of iterable) {
    console.log(x);
}

我个人并不知道这样做的利弊。只是通过每次生成器完成时重新分配迭代,它就像你期望的那样工作。

编辑:随着对这项工作的更多了解,我建议只使用像Azder这样的生成器:

const generator = function*(){
    yield 1;
    yield 2;
    yield 3;
}

for (let x of generator()) {
    console.log(x);
}

for (let x of generator()) {
    console.log(x);
}

我推荐的版本会阻止您在迭代失败时运行迭代......例如,如果您正在等待一个URL调用另一个URL。如果第一个网址失败,则必须先刷新您的应用,然后才能再次尝试收益。

答案 5 :(得分:3)

每当你需要"重置"一个可迭代的,只是把旧的扔掉,然后换一个新的。



var generator = function*() {
    yield 1;
    yield 2;
    yield 3;
};
const makeIterable = () => generator()

for (let x of makeIterable()) {
    console.log(x);
}

// At this point, iterable is consumed.
// Is there a method for moving iterable back
// to the start point by only without re-calling generator(),
// (or possibly by re-calling generator(), only by using prototype 
//  or constructor methods available within the iterable object)
// so the following code would work again?

for (let x of makeIterable()) {
    console.log(x);
}




答案 6 :(得分:0)

我认为这不是生成器的问题,而是迭代器的问题–实际上是“做工”。要重置迭代,您只需要创建一个新的迭代器即可。我可能会使用像这样的愚蠢的高阶函数:

rimraf

答案 7 :(得分:0)

没有,没有回到原来的状态。

为清楚起见,您必须了解生成器功能的工作。

当生成器函数第一次被调用时,它返回迭代器(作为它的整个主体)。 此返回迭代器的初始状态存储在其变量中。两个非常重要的变量是 GeneratorStatus, GeneratorLocation。

还有其他变量,例如GeneratorFunction,GeneratorReceiver,Scopes。理解该答案可以忽略不计。

因此初始状态为 GeneratorStatus:已暂停。 GeneratorLocation:1;

现在要使用迭代器,应使用.next();进行调用。 因此,第四迭代器将从“ GeneratorLocation”指向的位置恢复执行。

现在generator会将其GeneratorGeneratorLocation的值更新为第no行,它首先产生结果的位置,GeneratorLocation将保持不变,直到返回最后的yield。

现在,对于.next的每个后续调用,Generator将从GeneratorLocation的值而不是从开头开始执行。

因此,除非您重复生成器功能中的代码,否则无法将其重置为初始状态。最好的解决方案是使用参数重新创建新的迭代器。

答案 8 :(得分:0)

对于这个线程上的其他人来说,简短的回答是一个艰难的“不”。您不能将迭代器重置回其初始状态,尤其是由生成器生成的状态。根据您正在处理的生成器,再次调用生成器为您提供新的迭代器甚至可能会产生完全不同的结果集。

长答案是...是的,但不是。在传递结果之前,您需要迭代一次以将结果缓存到数组中(从而失去生成器的性能提升),或者您需要将其包装在一个类实例中,该类实例将处理内部状态和缓存结果迭代器。

这是一个示例实现,其中我创建了一个类似于数组的列表样式类,它允许我通过缓存结果并允许像数组一样进行多次调用来传递迭代器而无需再次调用它,同时仍然返回每个结果的相同状态和位置。

https://github.com/ahuggins-nhs/js-edi/blob/element-selectors/packages/dom/query/QueryEngineList.ts

答案 9 :(得分:0)

  • 您可以向生成器的 .next() 方法传递一个可选参数,您可以使用该参数重置生成器的状态。

  • yield 不仅会生成生成器可迭代对象的当前调用的状态,还会查找传递给生成器的状态。

  • 所以回答你的问题,是的,你可以将生成器重置为初始状态,只要它没有完成(完成仍然是假的)。在您的情况下,您必须将生成器代码更改为以下内容:

let generator = function* () {
  let count = 0;
  while (true) {
    count += 1;
    /*
    if you pass a paramater of value `true` to the iterable's next,
    the `yield` will be equal to true.
    Thus `reset` will be true and the if condition below will executed.
    Like that the yielded value `count` will be reset to its initial value of 0.
    */
    let reset = yield count;
    if (reset) {
      count = 0;
    }
  }
}

let iterable = generator();

console.log(iterable.next().value);  // 1
console.log(iterable.next().value);  // 2
console.log(iterable.next().value);  // 3
console.log(iterable.next().value);  // 4
console.log(iterable.next(true).value);  // 1 as you can see the count has been reset
console.log(iterable.next().value);  // 2
console.log(iterable.next().value); // 3

答案 10 :(得分:0)

如果您想生成数组的值并返回到第一项(在生成最后一项之后),您可以执行以下操作:

function * arrayIterator () {
    let i = 0
    while ( i < array.length) {
        yield array[i]
        if (i === array.length - 1) i = -1 //this will reset the loop to 0 with i++
        i++
    }
}



如果你想重置一个生成器,你可以再次初始化它。

let gen = arrayIterator

假设您想要 return 一个特定值。之后,您需要像这样重置生成器:

const newValue = gen.return("I was returned").value

gen = arrayIterator() // now you are back at the start, when you call gen.next().value


更新: 我找到了一个更简单的解决方案:

function * arrayIterator(){
    yield* array
}
let gen = arrayIterator()

yield* 返回下一个数组项,就好像它是它自己的生成器一样。

然后你可以像这样重置迭代:

let {value, done} = gen.next()
if (done === true) {
    gen = arrayIterator()
    value = gen.next().value // value at position 0
}