Geradores e Iteradores Assíncronos
Processe grandes conjuntos de dados, APIs paginadas e dados de streaming com geradores assíncronos em Componentes de Servidor e Manipuladores de Rota.
Busque em todas as páginas da documentação
Processe grandes conjuntos de dados, APIs paginadas e dados de streaming com geradores assíncronos em Componentes de Servidor e Manipuladores de Rota.
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥
Referência rápida para padrões de geradores assíncronos.
// Gerador assíncrono básico
async function* fetchAllPages(baseUrl: string) {
let page = 1;
let hasMore = true;
while (hasMore) {
const res = await fetch(`${baseUrl}?page=${page}`);
const data = await res.json();
yield data.items;
hasMore = data.hasNextPage;
page++;
}
}
// Consumindo com for-await-of
for await (const batch of fetchAllPages("/api/products")) {
processBatch(batch);
}Quando usar isso: Você precisa processar respostas de API paginadas, transmitir grandes conjuntos de dados sem carregar tudo na memória, ou produzir dados incrementalmente em um Manipulador de Rota.
// lib/paginated-fetch.ts
interface PaginatedResponse<T> {
items: T[];
nextCursor: string | null;
}
async function* fetchAllItems<T>(
url: string,
options?: RequestInit
): AsyncGenerator<T[], void, unknown> {
let cursor: string | null = null;
do {
const fetchUrl = cursor ? `${url}?cursor=${cursor}` : url;
const res = await fetch(fetchUrl, options);
const data: PaginatedResponse<T> = await res.json();
yield data.items;
cursor = data.nextCursor;
} while (cursor !== null);
}
// app/admin/export/route.ts - Transmitir exportação CSV
import { NextResponse } from "next/server";
interface Order {
id: string;
customer: string;
total: number;
date: string;
}
export async function GET() {
const encoder = new TextEncoder();
const stream = new ReadableStream({
async start(controller) {
// Cabeçalho CSV
controller.enqueue(encoder.encode("id,customer,total,date\n"));
// Transmitir linhas da API paginada
for await (const batch of fetchAllItems<Order>(
"https://api.example.com/orders"
)) {
for (const order of batch) {
const row = `${order.id},${order.customer},${order.total},${order.date}\n`;
controller.enqueue(encoder.encode(row));
}
}
controller.close();
},
});
return new NextResponse(stream, {
headers: {
"Content-Type": "text/csv",
"Content-Disposition": 'attachment; filename="orders.csv"',
},
});
}O que isso demonstra:
async function* e yield para produzir valoresyield pausa o gerador e retorna um valor para o consumidorfor await...of para iterar sobre os valores produzidosyield ou return| Conceito | Sintaxe | Descrição |
|---|---|---|
| Função geradora assíncrona | async function* name() | Define um gerador que produz promessas |
yield | yield value | Pausa e produz um valor |
yield* | yield* otherGenerator() | Delega para outro gerador |
for await...of | for await (const x of gen()) | Consome um iterável assíncrono |
return | return value | Encerra o gerador |
.next() | gen.next() | Avança manualmente o gerador |
.return() | gen.return() | Termina o gerador antecipadamente |
API paginada com offset:
async function* paginateWithOffset<T>(
fetcher: (offset: number, limit: number) => Promise<T[]>,
limit = 100
): AsyncGenerator<T[]> {
let offset = 0;
while (true) {
const batch = await fetcher(offset, limit);
if (batch.length === 0) break;
yield batch;
if (batch.length < limit) break; // Última página
offset += limit;
}
}
// Uso em um Componente de Servidor
export default async function AllProductsPage() {
const allProducts: Product[] = [];
for await (const batch of paginateWithOffset(
(offset, limit) =>
db.product.findMany({ skip: offset, take: limit }),
50
)) {
allProducts.push(...batch);
}
return <ProductGrid products={allProducts} />;
}Transmissão de Server-Sent Events (SSE):
// app/api/events/route.ts
export async function GET() {
const encoder = new TextEncoder();
async function* eventStream() {
let id = 0;
while (true) {
const data = await getLatestUpdate();
yield `id: ${id++}\ndata: ${JSON.stringify(data)}\n\n`;
await new Promise((resolve) => setTimeout(resolve, 1000));
}
}
const stream = new ReadableStream({
async start(controller) {
for await (const event of eventStream()) {
controller.enqueue(encoder.encode(event));
}
},
});
return new NextResponse(stream, {
headers: {
"Content-Type": "text/event-stream",
"Cache-Control": "no-cache",
Connection: "keep-alive",
},
});
}Gerador de Transformação (padrão de pipeline):
// Encadear geradores para pipelines de transformação de dados
async function* map<T, U>(
source: AsyncIterable<T>,
transform: (item: T) => U | Promise<U>
): AsyncGenerator<U> {
for await (const item of source) {
yield await transform(item);
}
}
async function* filter<T>(
source: AsyncIterable<T>,
predicate: (item: T) => boolean | Promise<boolean>
): AsyncGenerator<T> {
for await (const item of source) {
if (await predicate(item)) yield item;
}
}
async function* take<T>(
source: AsyncIterable<T>,
count: number
): AsyncGenerator<T> {
let taken = 0;
for await (const item of source) {
yield item;
if (++taken >= count) break;
}
}
// Pipeline: buscar todos os usuários -> filtrar ativos -> pegar os 10 primeiros -> transformar
const pipeline = take(
map(
filter(
fetchAllItems<User>("https://api.example.com/users"),
(users) => users.filter((u) => u.active).length > 0
),
(users) => users.filter((u) => u.active)
),
10
);Coletando todos os resultados:
// Auxiliar para coletar um iterável assíncrono em um array
async function collect<T>(iterable: AsyncIterable<T[]>): Promise<T[]> {
const results: T[] = [];
for await (const batch of iterable) {
results.push(...batch);
}
return results;
}
// Uso
const allOrders = await collect(
fetchAllItems<Order>("https://api.example.com/orders")
);Gerador com limitação de taxa:
async function* rateLimited<T>(
source: AsyncIterable<T>,
delayMs: number
): AsyncGenerator<T> {
for await (const item of source) {
yield item;
await new Promise((resolve) => setTimeout(resolve, delayMs));
}
}
// Buscar páginas com 200ms de atraso entre requisições
for await (const batch of rateLimited(fetchAllPages(url), 200)) {
processBatch(batch);
}// Tipando geradores assíncronos
async function* counter(): AsyncGenerator<number, void, unknown> {
// AsyncGenerator<Yield, Return, Next>
// Yield = tipo dos valores produzidos por yield
// Return = tipo do valor de retorno
// Next = tipo dos valores passados para .next()
let i = 0;
while (true) {
yield i++;
}
}
// AsyncIterable é o tipo do lado do consumidor
async function processItems(source: AsyncIterable<string[]>) {
for await (const batch of source) {
console.log(batch.length);
}
}
// Tipo de fetcher paginado genérico
type PaginatedFetcher<T> = (
cursor: string | null
) => Promise<{ items: T[]; nextCursor: string | null }>;Geradores são preguiçosos. Nada é executado até que você consuma o gerador com for await...of ou .next(). Se você criar um gerador, mas nunca iterá-lo, o corpo da função nunca será executado. Correção: Isso é um recurso, não um bug. Apenas lembre-se de consumi-lo.
Tratamento de erros em for-await-of. Se um yield lançar um erro, o loop termina e o erro se propaga. Itens não consumidos são perdidos. Correção: Envolva o loop em try/catch, ou trate erros dentro do gerador com try/catch em torno de yield.
Vazamentos de memória com geradores infinitos. Um gerador que nunca retorna mantém seu closure vivo. Correção: Use break no consumidor ou .return() no gerador para permitir a limpeza. for await...of chama .return() automaticamente em break.
Não é possível usar geradores em Componentes de Cliente. Geradores são executados apenas no servidor. Componentes de Cliente precisam dos dados finais ou de um padrão de streaming (SSE, WebSocket). Correção: Use geradores apenas em Componentes de Servidor, Manipuladores de Rota ou Ações de Servidor.
Sem backpressure por padrão. O gerador produz tão rápido quanto o consumidor solicita, mas se você produzir para um stream, o stream pode ser bufferizado. Correção: Adicione atrasos com rateLimited ou use ReadableStream com backpressure baseado em pull.
Geradores são de uso único. Uma vez consumido, você não pode iterar um gerador novamente. Chamar a função geradora cria um novo iterador. Correção: Chame a função geradora novamente para um novo iterador.
| Alternativa | Usar Quando | Não Usar Quando |
|---|---|---|
Promise.all | Número fixo de fetches paralelos | Número desconhecido de páginas |
| Array + loop | Fetches sequenciais simples com contagem conhecida | Conjuntos de dados grandes ou ilimitados |
ReadableStream | Transmissão de respostas HTTP (SSE, downloads de arquivos) | Agregação simples de dados |
| Cursors de banco de dados | Paginação direta de banco de dados (Prisma, SQL) | Paginação de API externa |
| Web Streams API | Streaming compatível com navegador | Processamento apenas no servidor |
Promise que resolve para um valorasync function* e yield para produzir múltiplos valores ao longo do tempofor await...offor await...of ou chamar .next() manualmente para iniciar a execuçãoasync function* counter(): AsyncGenerator<number, void, unknown> {
// AsyncGenerator<Yield, Return, Next>
// Yield = tipo dos valores produzidos
// Return = tipo do valor final retornado
// Next = tipo dos valores passados para .next()
let i = 0;
while (true) yield i++;
}yield lançar um erro, o loop termina e o erro se propagayieldyield value pausa o gerador e produz um único valoryield* otherGenerator() delega para outro gerador, produzindo todos os seus valoresyield* é útil para compor geradoresbreak no loop do consumidor ou chame .return() no geradorfor await...of chama .return() automaticamente quando você usa breakexport async function GET() {
const encoder = new TextEncoder();
const stream = new ReadableStream({
async start(controller) {
controller.enqueue(encoder.encode("id,name\n"));
for await (const batch of fetchAllItems<Item>(url)) {
for (const item of batch) {
controller.enqueue(encoder.encode(`${item.id},${item.name}\n`));
}
}
controller.close();
},
});
return new NextResponse(stream, {
headers: { "Content-Type": "text/csv" },
});
}AsyncIterable<T> é o tipo do lado do consumidor -- qualquer coisa que você pode usar com for await...ofAsyncGenerator<T> é o tipo do lado do produtor retornado por async function*AsyncIterable em parâmetros de função para flexibilidadeasync function* rateLimited<T>(
source: AsyncIterable<T>,
delayMs: number
): AsyncGenerator<T> {
for await (const item of source) {
yield item;
await new Promise((r) => setTimeout(r, delayMs));
}
}Revisado por Chris St. John·Última atualização: 7 de jul. de 2026
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥