Reputation: 1618
I'm playing around with ES6 Generators, because they've been quite the hype recently. My goal is to have a generator which yields a subset of a larger generator and stops. However, when the generator is called again, instead of repeating the sequence, it continues. Much like an ES6 Generator. In other words, I have a nested generator.
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);
What I'm expecting this code to do is print the numbers 0-10, print Done
, then print 10-20, print Done
and so on. However, the actual output is 0-10 then Done
repeatedly. I'm not sure why, nor how I would get the result I'm looking for.
Upvotes: 1
Views: 243
Reputation: 1074088
It's because of the return
in the for (const j of gen) {
of b
, and the fact that you have for (const j of b(gen, 10))
called in a loop.
When you return
out of a for-of
loop, it calls the return
method of the generator being iterated (gen
in this case, the one from a
). That finishes the generator, so all subsequent attempts to use that generator (because the calls to b
reuse gen
and those calls are in a loop) just keep returning that same value. (break
from the loop will also call return
on the generator.)
Here's a simpler example:
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;
}
If you don't want that automatic behavior of for-of
, you might call next
on the generator directly rather than using for-of
. (If you want 0-9
, "Done"
, 10-19
, "Done"
, etc, you also have to tweak your counter and move where you're outputting "Done"
.) Example:
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;
}
Alternatively, you could wrap the generator in an object that only forwarded next
calls (and implemented [Symbol.iterator]
):
const continuingIterator = it => {
return {
[Symbol.iterator]() {
return this;
},
next() {
return it.next();
},
};
};
Then it would be:
for (const j of continuingIterator(gen)) {
Live Example:
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;
}
Upvotes: 4