Iterate paginated API results with async generators
TypeScript
// Walk cursor-paginated pages with for await, stopping on a repeated cursor.
type Page<T> = { items: T[]; nextCursor: string | null };
async function* paginate<T>(
loadPage: (cursor: string | null) => Promise<Page<T>>,
): AsyncGenerator<T, void, undefined> {
let cursor: string | null = null;
const seen = new Set<string>();
for (;;) {
const page = await loadPage(cursor);
yield* page.items;
if (!page.nextCursor) return;
if (seen.has(page.nextCursor)) {
throw new Error(`pagination loop detected at cursor "${page.nextCursor}"`);
}
seen.add(page.nextCursor);
cursor = page.nextCursor;
}
}
async function loadPage(cursor: string | null): Promise<Page<{ id: number }>> {
const start = cursor ? Number(cursor) : 0;
const items = [start, start + 1].map((id) => ({ id }));
const nextCursor = start + 2 < 6 ? String(start + 2) : null;
return { items, nextCursor };
}
(async () => {
const ids: number[] = [];
for await (const item of paginate(loadPage)) ids.push(item.id);
console.log(ids); // [0, 1, 2, 3, 4, 5]
})();