Search code examples
javascriptecmascript-6iteratorgenerator

How do I reuse a generator in ES6 javascript in for loops?


I'm trying to write a function that can take either a list or a generator as input. For example, this function:

function x(l) {
    for (let i of l) {
        console.log(i);
    }
    for (let i of l) {
        console.log(i);
    }
}

If I run it like this:

x([1,2,3])

It will display:

1
2
3
1
2
3

Now I want to use a generator as input:

function *y() {
    yield 5
    yield 6
    yield 7
}

These don't work:

x(y())
x(y)

The output is:

5
6
7
undefined

What do I need to do so that I can make it work?

In terms of Java, the function y above is a Generator and y() is an Iterator. [1,2,3] is a list and in Java, lists are generators. But the javascript for loop expects an iterator, which means that it can't be restarted. This seems like a flaw in javascript that the for loop works on iterators and not generators.


Solution

  • A generator cannot be used multiple times. If you want to iterate it twice, you will need to create two generators by calling the generator function twice.

    What you can do when your function expects an iterable (that is used in a for … of loop) is to create one on the fly from your generator function:

    x({[Symbol.iterator]: y})
    

    If you want to write your function x so that it can take either an iterator or a generator function, you can use something like

    getIterator(val) {
        if (typeof val == "function") // see also  https://stackoverflow.com/q/16754956/1048572
            return val();
        if (typeof val[Symbol.iterator] == "function")
            return val[Symbol.iterator]();
        throw new TypeError("not iterable!")
    }
    function x(l) {
        for (let i of getIterator(l)) {
            console.log(i);
        }
        for (let i of getIterator(l)) { // call getIterator again
            console.log(i);
        }
    }
    x(y);