5

I have written this code to iterate over github issues with a specific number (like pagination), in this case with 3 issues at once:

const getUrl = (page) => `https://api.github.com/repos/angular/angular/issues?page=${page}`;

const getIssues = async function*() {
    for (let p = 1; true; p++) {
        const url = getUrl(p);
        const rawData = await fetch(url, {
            headers: { 'User-Agent': 'app' }
        });
        const issues = await rawData.json();
        for (let issue of issues) {
            yield issue;
        }
    }
};

const generator = getIssues();

document.querySelector('[data-next]').addEventListener('click', async function() {
    let i = 0;
    for await (let issue of generator) {
        console.log(issue);
        if (++i === 3) break;
    }
    console.log(await generator.next());
});

The element with data-next attribute is a button. The expected behavior is every click on the button loads the next 3 issues. The problem is, the generator finished after the break (the console.log prints this: {value: undefined, done: true}).

Why it is finished, and how could I make this work as expected?

Herbertusz
  • 1,151
  • 1
  • 11
  • 19

1 Answers1

3

It's a known problem/feature, that for..of terminates the generator (see e.g. here). One possible solution is to provide a proxy which will persist the actual generator state in a closure:

function persist(gen) {
    return {
        next() {
            return gen.next()
        },
        [Symbol.asyncIterator]() {
            return this
        },
        [Symbol.iterator]() {
            return this
        }
    }
}

//

const getUrl = (page) => `https://jsonplaceholder.typicode.com/posts/${page}/comments`;

const getIssues = async function* () {
    for (let p = 1; true; p++) {
        const url = getUrl(p)
        const raw = await fetch(url)
        const data = await raw.json()
        yield* data
    }
};

async function main() {

    const generator = persist(getIssues());
    let i = 0;

    for await (let x of generator) {
        console.log(i, x.postId, x.id, x.name);
        if (++i === 4) break;
    }

    console.log('break'); i = 0;

    for await (let x of generator) {
        console.log(i, x.postId, x.id, x.name);
        if (++i === 4) break;
    }

    console.log('break'); i = 0;

    for await (let x of generator) {
        console.log(i, x.postId, x.id, x.name);
        if (++i === 4) break;
    }

}

main()
georg
  • 211,518
  • 52
  • 313
  • 390
  • 1
    Thanks for marking my answer as accepted, however, don't be hasty with that. This is an interesting question and might attract more competent posters, which would be demotivated seeing you've already accepted. Let it remain "unanswered" for a couple of hours at least. – georg Apr 21 '21 at 10:52
  • Ok. By the way, through your link I've found this in the reference: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for...of#do_not_reuse_generators The problem was I wasn't able to found this in case of the for-await loop. – Herbertusz Apr 21 '21 at 11:07