Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What is the best practice to create an async iterator? Should I use an async generator function or rather use Symbol.asyncIterator?

This code works as expected:

function sleep(ms) {
    return new Promise(resolve => setTimeout(resolve, ms));
}

async function getAsyncData() {
    await sleep(1000);  // simulate database/network delay...
    return [1, 2, 3, 4, 5];  // ...then return some data
}

const asyncIterable = (async function* filterAsyncData() {
    const items = await getAsyncData();

    for (const item of items) {
        yield item;
    }
})();

const asyncIterable2 = {
    [Symbol.asyncIterator]() {
        return {
            values: null,
            idx: 0,
            async next() {
                if (this.values === null) {
                    this.values = await getAsyncData();
                }

                if (this.idx < this.values.length) {
                    this.idx = this.idx + 1;
                    return Promise.resolve({ value: this.values[this.idx - 1], done: false });
                }

                return Promise.resolve({ done: true });
            }
        };
    }
};

async function main() {
    for await (const filteredItem of asyncIterable) {
        console.log(filteredItem);
    }
}

main()

It does not mather if I use asyncIterable or asyncIterable2 in the main function, I always get the same result. What is the best practice to define my iterable? Are there any guidelines about which option is preferred? Why?

like image 494
enanone Avatar asked Sep 04 '25 16:09

enanone


1 Answers

It's the same as for synchronous iterators: generator functions are much easier to write, and easier to get correct, than implementing the iterator object manually. Do this only if you need some non-standard behaviour that cannot be achieved otherwise. With asynchronous generator functions specifically, you even get the proper queueing of next calls for free, which is a real headache to get right (your asyncIterable2 fails this1).

The most common implementation of iterables is to make the Symbol.asyncIterator method an async generator method:

const asyncIterable = {
    async *[Symbol.asyncIterator]() {
         yield* await getAsyncData();
    },
};

1: const it = asyncIterable2[Symbol.asyncIterator](); it.next(); it.next() - without any awaits in between - will call getAsyncData twice, because this.values == null in both calls

like image 162
Bergi Avatar answered Sep 07 '25 08:09

Bergi