javascriptecmascript-6generator

How to iterate over a generator with indexes?


With arrays in javascript, getting the current index for iteration is easy. You can either use forEach and the index is the second entry, or use for...of and .entries() and array unpacking.

But generators have no .entries() method. How do I get the current index for a generator in my for...of loop?

I basically want:

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

for(let [j, index] of myGen().entries()) { //<-- I want .entries() but for a Generator
    //...
}
//Running the above produces TypeError: myGen(...).entries(...) is not a function or its return value is not iterable

Solution

  • In 2024 I'm updating this answer from 2018 with the mention of the Iterator helpers feature in stage 3 that is getting support (Node 22.9+, Chrome/Edge 122+, Opera 108+). The set of Iterator prototype functions includes a forEach method, which provides the index to the callback function:

    // Demo
    function* myGen(){
        let i = 64;
        while(i < 70) {
            i+=1;
            yield String.fromCharCode(i);
        }
    }
    
    myGen().forEach((value, index) => {
        console.log(value, index);
    });


    Original answer from 2018:

    It is not advisable to add things to a built-in prototype, but if you really want your code to work like that (calling .entries() on any generator), then you could proceed as follows:

    const Generator = Object.getPrototypeOf(function* () {});
    
    Generator.prototype.entries = function * () {
        let i = 0;
        for (let value of this) {
            yield [i++, value];
        }
    }
    
    // Demo
    function* myGen(){
        let i = 64;
        while(i < 70) {
            i+=1;
            yield String.fromCharCode(i);
        }
    }
    
    for(let [index, value] of myGen().entries()) { //<-- Now you have .entries() on a Generator
        console.log(index, value);
    }

    It is more prudent however to define a utility function.

    const GeneratorUtils = {
        * entriesOf(iter) {
            let i = 0;
            for (let value of iter) {
                yield [i++, value];
            }
        }
    };
    
    // Demo
    function* myGen(){
        let i = 64;
        while(i < 70) {
            i+=1;
            yield String.fromCharCode(i);
        }
    }
    
    for(let [index, value] of GeneratorUtils.entriesOf(myGen())) {
        console.log(index, value);
    }