为什么这个生成器函数会中断?

Why does this generator function break?

我正在研究 ES6 生成器,因为它们最近大肆宣传。我的目标是拥有一个生成较大生成器的子集并停止的生成器。然而,当再次调用生成器时,它不会重复序列,而是继续。很像 ES6 生成器。换句话说,我有一个嵌套的生成器。

const a = function* (): Generator<number> {
    for (let i = 0; i < 100; i++)
        yield i;

    for (const i of a())
        yield i;
}

const b = function* (gen: Generator<number>, subsetSize: number): Generator<number> {
    let i = 0;
    for (const j of gen) {
        if (i++ > subsetSize)
            return;
        else
            yield j;
    }
    console.log("Done");
}

const gen = a();

for (let i = 0; i < 150; i++)
    for (const j of b(gen, 10))
        console.log(j);

我期望此代码执行的操作是打印数字 0-10,打印 Done,然后打印 10-20,打印 Done 等等。然而,实际输出是0-10然后Done重复。我不确定为什么,也不知道如何获得我正在寻找的结果。

这是因为 bfor (const j of gen) { 中的 return,以及循环调用 for (const j of b(gen, 10)) 的事实。

当您 return 退出 for-of 循环时,它会调用正在迭代的生成器的 return methodgen 在这种情况下,来自 a).这完成了生成器,因此所有后续尝试使用该生成器(因为对 b 的调用重用 gen 并且这些调用处于循环中)只是​​继续返回相同的值。 (循环中的 break 也会调用生成器上的 return。)

这是一个更简单的例子:

function* example() {
    let i = 0;
    while (true) {
        yield i++;
    }
}

function use(gen) {
    let counter = 0;
    console.log("Starting for-of on gen");
    for (const value of gen) {
        console.log(value);
        if (value > 5) {
            console.log("Returning early");
            return;
        }
    }
    console.log("Done with for-of on gen");
}

const gen = example();
use(gen);
use(gen);
use(gen);
.as-console-wrapper {
    max-height: 100% !important;
}

如果您不想要 for-of 的自动行为,您可以直接在生成器上调用 next 而不是使用 for-of。 (如果你想要 0-9"Done"10-19"Done" 等,你还必须调整你的计数器并移动你输出 "Done" 的位置。 ) 例子:

const a = function* ()/*: Generator<number>*/ {
    for (let i = 0; i < 100; i++)
        yield i;

    for (const i of a())
        yield i;
}

const b = function* (gen/*: Generator<number>*/, subsetSize/*: number*/)/*: Generator<number>*/ {
    let i = 0;
    let result;
    while (!(result = gen.next()).done) {
        yield result.value;
        if (++i >= subsetSize) {
            console.log("Done");
            return;
        }
    }
}

const gen = a();

for (let i = 0; i < /*150*/3; i++)
    for (const j of b(gen, 10))
        console.log(j);
.as-console-wrapper {
    max-height: 100% !important;
}

或者,您可以将生成器包装在一个仅转发 next 调用(并实现 [Symbol.iterator])的对象中:

const continuingIterator = it => {
    return {
        [Symbol.iterator]() {
            return this;
        },
        next() {
            return it.next();
        },
    };
};

那么就是:

for (const j of continuingIterator(gen)) {

实例:

const continuingIterator = it => {
    return {
        [Symbol.iterator]() {
            return this;
        },
        next() {
            return it.next();
        },
    };
};

const a = function* ()/*: Generator<number>*/ {
    for (let i = 0; i < 100; i++)
        yield i;

    for (const i of a())
        yield i;
}

const b = function* (gen/*: Generator<number>*/, subsetSize/*: number*/)/*: Generator<number>*/ {
    let i = 0;
    for (const j of continuingIterator(gen)) {
        yield j;
        if (++i >= subsetSize) {
            console.log("Done");
            return;
        }
    }
}

const gen = a();

for (let i = 0; i < /*150*/3; i++)
    for (const j of b(gen, 10))
        console.log(j);
.as-console-wrapper {
    max-height: 100% !important;
}