I think that something exposing an async iterator would be cleaner to use.
let count = 0;
let queue = [];
for await (const snapshot of createTraverser(...)) {
count++;
queue.push(sendEmail(snapshot.data());
if (queue.length >= 50) {
await promise.all(queue);
queue = [];
}
}
await promise.all(queue);
console.log(`Processed ${count} users.`);
The items can simply implement the async iterator methods, and fetch in batches internally.