admin管理员组

文章数量:1414851

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

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
Share Improve this question asked Dec 8, 2018 at 21:37 CobertosCobertos 2,26327 silver badges45 bronze badges
Add a ment  | 

4 Answers 4

Reset to default 5

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

There's no built-in way to do it - the generator will have to yield something that contains the index. For example:

function* myGen(){
  let index = 0;
    while(index < 10) {
      const item = 'foo' + index;
      yield { item, index };
      index++;
    }
}

for(const { item, index } of myGen()) {
  console.log('item: ' + item);
  console.log('index: ' + index);
}

If you can't modify a generator that you want to also get the index of, you can put it inside another generator that does keep track of the index (or you could just increment on every iteration outside):

function* unmodifiableGen(){
  // index is private, is not being yielded
  let index = 0;
  while(index < 10) {
    yield Math.random();
    index++;
  }
}
function* generatorCounter(gen) {
  // this index *will* be yielded:
  let index = 0;
  for (const item of gen()) {
    yield { item, index };
    index++;
  }
}

for(const { item, index } of generatorCounter(unmodifiableGen)) {
  console.log('item: ' + item);
  console.log('index: ' + index);
}

A slightly different approach might be to make myGen() a regular function that returns an object adhering to the iterator protocol rather than a generator. Then you can just give it an entries() method. It will work a little differently than a generator (you can't call next() on it directly). But it be self-contained and should work as expected in situations where an iterator is expected:

function myGen(start, stop){
   return  {
        [Symbol.iterator]: function* () {
            while(start < stop){
                yield start++
            }
        },
        entries: function* entries (){
            let i = 0
            for (n of this){
                yield [i++, n]
            }
        }
    }
}

 
let g = myGen(10, 20)
// works like a regular iterator:
console.log([...g])

// but you can also call entries():
g = myGen(2, 9)
for ([i, n] of g.entries()){
  console.log(`index: ${i}, value: ${n}`)
}

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

You can utilize spread element preceding generator function call within an array literal and .entries() method of Array.prototype

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

for (const [index, value] of [...myGen()].entries()) {
  console.log(index, value);
}

本文标签: javascriptHow to iterate over a generator with indexesStack Overflow