[ACCEPTED]-Is it possible to reset an ECMAScript 6 generator to its initial state?-ecmascript-6

Accepted answer
Score: 26

If your intention is

to some other scope, iterate 6 over it, do some other stuff, then be able 5 to iterate over it again later on in that 4 same scope.

Then the only thing you shouldn't 3 try doing is passing the iterator, instead 2 pass the generator:

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);
    }
}

Or just make a "round 1 robin" iterator and check while iterating

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

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

At this point, iterable is consumed.

Which 14 means its internal [[GeneratorState]] is 13 completed.

Is there a method for moving iterable back 12 to the start point by only without re-calling 11 generator()

No. The spec states

Once a generator enters the "completed" state it never leaves it and its associated execution context is never resumed. Any execution state associated with generator can be discard at this point.

or possibly 10 by re-calling generator(), only by using 9 prototype or constructor methods available 8 within the iterable object

No. While not 7 explicitly stated in the spec, there are 6 no more instance-specific properties available 5 on the iterable object than [[GeneratorState]] and [[GeneratorContext]].

However, the 4 informative "Generator Object Relationships" grapic states:

Each Generator Function has an associated prototype that does not have a constructor property. Hence a generator instance does not expose access to its generator function.

I would like to be able 3 to pass the iterable off to some other scope

Pass 2 the generator function instead. Or something 1 that yields new generator instances.

Score: 6

Whenever you need to "reset" an iterable, just 1 toss the old one away and make a new one.

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);
}
Score: 5

As best I can tell that isn't possible. Per 33 this useful wiki and the draft version of ES6 on generators, once you've returned 32 from it (rather than yielded), it puts it 31 into the "closed" state and there is no way to move 30 it back to the "newborn" state which is how a new 29 generator starts out.

You may have to pass 28 along a callback to your other scope for 27 creating a new generator. As a work-around, you 26 could even add that callback as a custom 25 method on the generator you sent to the 24 other scope if you wanted and that callback 23 would create a new generator for the other 22 scope.

If you think about how generators 21 work, they'd have to execute over from scratch 20 to reset their initial state and there's 19 simply no reason to support that. That 18 would be analagous to asking why you can't 17 just re-execute the constructor on an existing 16 object and expect to have a virgin object 15 in the same object. While it's all technically 14 doable, it's hairy to make work right and 13 there's really no reason to support it. If 12 you want a virgin object, just create a 11 new one. Same with a generator.


This is 10 a bit of a hack, but a curious thing to 9 contemplate. You could make a generator 8 that repeated itself. Suppose your generator 7 worked like this:

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

I can easily see how this 6 might be an anti-pattern though because 5 if you ever do this:

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

You will have an infinite 4 loop, so it would have to be used with great 3 care. FYI, the above wiki shows examples 2 of an infinite Fibonacci sequence so an 1 infinite generator is certainly contemplated.

Score: 5

As per the draft version of ES6,

Once a generator enters the 7 "completed" state it never leaves it and its associated 6 execution context is never resumed. Any 5 execution state associated with generator 4 can be discard at this point.

So, there is 3 no way to reset it once it is completed. It 2 also makes sense to be so. We call it a 1 generator, for a reason :)

Score: 3

You can also have your generator reset your 13 iterable like this:

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);
}

I do not personally know 12 the pros and cons of doing this. Just that 11 it works as you would expect by reassigning 10 the iterable each time the generator finishes.

EDIT: With 9 more knowledge of how this work I would 8 recommend just using the generator like 7 Azder Showed:

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);
}

The version I recommended will prevent 6 you from being able to run through the iterations 5 if it ever fails... For example if you were 4 waiting on one url to call another. If the 3 first url fails you would have to refresh 2 your app to before it would be able to try 1 that first yield again.

Score: 1

I think this is not a concern of a generator, but 4 of an iterator – which actually 'does the 3 labour'. To reset an iteration you just 2 need to create a new iterator. I would probably 1 use a dumb higher order function like this:

function *foo() {
    yield 1;
    yield 2;
    yield 3;
}

const iterateFromStart = (func) => {
    // every invocation creates a brand new iterator   
    const iterator = func();
    for (let val of iterator) {
        console.log(val)
  }
}

iterateFromStart(foo); // 1 2 3
iterateFromStart(foo); // 1 2 3
Score: 1

Per everyone else on this thread, the short 21 answer is a hard "no". You cannot 20 reset an iterator back to it's initial state, ESPECIALLY 19 one produced by a generator. Depending on 18 what generator you're dealing with, calling 17 the generator again to give you a new iterator 16 might even produce a completely different 15 set of results.

The long answer is... yes, but 14 no. You either need to iterate once to cache 13 the results into an array before passing 12 the results around (thus losing the performance 11 gains from the generator), or you need to 10 wrap it in a class instance that will handle 9 internal state and caching of results from 8 the iterator.

Here's an example implementation 7 where I created a list-style class that 6 is LIKE an array, which allows me to pass 5 the iterator around without calling it again 4 by caching the results and allowing multiple 3 calls to be made LIKE an array, while still 2 returning the same state and position of 1 each result.

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

Score: 1
  • You can pass to the .next() method of the 10 generator an optional parameter that you 9 can use to reset the state of the generator.

  • The 8 yield doesn't only produce the state of 7 the current call of the generator's iterable, but 6 also looks for the state passed to the generator.

  • So 5 to answer your question, Yes you can reset 4 the generator to its initial state as long 3 as its not been completed (done is still 2 false). In your case, you'll have to change 1 your generator code to the following:

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

Score: 0

No there is no going back to same state.

To 23 make it clear, you must understand the working 22 of generator functions.

When generator function 21 gets called first time, it returns iterator(as 20 its entire body). Initial status of this 19 return iterator is get stored in its variables. Two 18 very important variables are GeneratorStatus, GeneratorLocation.

There 17 are are other variables such as GeneratorFunction, GeneratorReceiver, Scopes. Which 16 can ignored to understand this answer.

So 15 initial status will be, GeneratorStatus 14 : suspended. GeneratorLocation: 1;

Now to 13 use iterator you should call it using .next(); Hence 12 forth iterator will resume its execution 11 from location pointed by 'GeneratorLocation'

Now 10 generator will update its value of GeneratorLocation 9 to line no where it first yields the result 8 and GeneratorLocation will be same until 7 it returns last yield.

Now for Each successive 6 calls of .next, Generator will resume its 5 execution from value of GeneratorLocation 4 and not from the beginning.

Hence, Unless 3 you repeat the code in generator function, resetting 2 to initial state is not possible. best solution 1 is just recreate new iterator with params.

Score: 0

If you want to yield values of an array 10 and go back to the first item(after the 9 last one has been yielded), you can do something 8 like this:

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++
    }
}



If you want to reset a generator 7 you can initialize it again.

let gen = arrayIterator

Let's say you 6 wanted to return a specific value. Afterward, you 5 will need to reset the generator like this:

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

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


UPDATE: I 4 found an even simpler solution:

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

yield* returns 3 the next array item as if it were it's own 2 generator.

Then you can reset the iteration 1 like this:

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

More Related questions