web-dev-qa-db-ja.com

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

反復可能オブジェクトを他のスコープに渡し、反復処理し、他の処理を実行してから、同じスコープで後で再度反復処理できるようにしたいと思います。

31
dvlsg

あなたの意図が

他のスコープに対して、それを繰り返し処理し、他の処理を実行してから、同じスコープで後でもう一度繰り返すことができます。

次に、実行してはいけない唯一のことは、イテレータを渡すことです。代わりに、ジェネレータを渡します。

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;
    }
}
16
Azder

この時点で、iterableが消費されます。

つまり、その内部[[GeneratorState]]はcompletedです。

Generator()を再度呼び出さずに、イテラブルを開始点に戻す方法はありますか?

いいえ。仕様には

ジェネレーターが「完了」状態に入ると、ジェネレーターを離れることはなく、関連する実行コンテキストが再開されることもありません。ジェネレータに関連付けられている実行状態は、この時点で破棄できます。

または、iterableオブジェクト内で使用可能なプロトタイプまたはコンストラクターメソッドを使用することによってのみ、generator()を再呼び出しすることによって

いいえ。仕様には明示的に記載されていませんが、 iterable object で使用できるインスタンス固有のプロパティは[[GeneratorState]]と[[GeneratorContext]]以外にありません。

ただし、有益な "Generator Object Relationships" grapic は次のように述べています。

各ジェネレーター関数には、コンストラクタープロパティを持たないプロトタイプが関連付けられています。したがって、ジェネレータインスタンスはそのジェネレータ関数へのアクセスを公開しません。

反復可能ファイルを他のスコープに渡すことができるようにしたいと思います

代わりに、ジェネレーター関数を渡します。または、新しいジェネレータインスタンスを生成するもの。

13
Bergi

私が知る限り、それは不可能です。 この便利なwiki および ドラフトバージョンの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);
}

無限ループになるので、細心の注意を払って使用する必要があります。参考までに、上記のwikiは無限フィボナッチ数列の例を示しているため、無限ジェネレーターが確実に検討されます。

4
jfriend00

ES6のドラフトバージョン に従って、

ジェネレータが"completed"状態に入ると、ジェネレータがそれを離れることはなく、関連する実行コンテキストが再開されることもありません。ジェネレータに関連付けられている実行状態は、この時点で破棄できます。

したがって、完了したらリセットする方法はありません。そうすることも理にかなっています。理由から、これをジェネレーターと呼びます:)

4
thefourtheye

次のように、ジェネレータにiterableをリセットさせることもできます。

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を呼び出すのを待っていた場合などです。最初のURLが失敗した場合、最初のyieldを再試行できるようになる前に、アプリを更新する必要があります。

3
William Randol

反復可能ファイルを「リセット」する必要があるときはいつでも、古いものを捨てて新しいものを作成するだけです。

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);
}
3
Jon z

これはジェネレーターの問題ではなく、イテレーターの問題だと思います。これは実際には「労力を費やします」。反復をリセットするには、新しいイテレータを作成する必要があります。私はおそらく次のようなばかげた高階関数を使用します:

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
0
HynekS