Is it possible to reset an ECMAScript 6 generator to its initial state?

孤街醉人 提交于 2019-12-17 09:40:21

问题


Given the provided (very simple) generator, is it possible to return the generator back to its original state to use again?

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

I would like to be able to pass the iterable off to some other scope, iterate over it, do some other stuff, then be able to iterate over it again later on in that same scope.


回答1:


If your intention is

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

Then the only thing you shouldn't try doing is passing the iterator, instead 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 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;
    }
}



回答2:


At this point, iterable is consumed.

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

Is there a method for moving iterable back to the start point by only without re-calling 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 by re-calling generator(), only by using prototype or constructor methods available within the iterable object

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

However, the 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 to pass the iterable off to some other scope

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




回答3:


As per the draft version of ES6,

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.

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




回答4:


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

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

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


This is a bit of a hack, but a curious thing to contemplate. You could make a generator that repeated itself. Suppose your generator 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 might be an anti-pattern though because if you ever do this:

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

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




回答5:


Whenever you need to "reset" an iterable, just 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);
}



回答6:


You can also have your generator reset your 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 the pros and cons of doing this. Just that it works as you would expect by reassigning the iterable each time the generator finishes.

EDIT: With more knowledge of how this work I would recommend just using the generator like 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 you from being able to run through the iterations if it ever fails... For example if you were waiting on one url to call another. If the first url fails you would have to refresh your app to before it would be able to try that first yield again.




回答7:


I think this is not a concern of a generator, but of an iterator – which actually 'does the labour'. To reset an iteration you just need to create a new iterator. I would probably 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


来源:https://stackoverflow.com/questions/23848113/is-it-possible-to-reset-an-ecmascript-6-generator-to-its-initial-state

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!