Promises Paralelas e Promise.all
Busque múltiplas fontes de dados em paralelo para eliminar waterfalls e acelerar a renderização do servidor.
Busque em todas as páginas da documentação
Busque múltiplas fontes de dados em paralelo para eliminar waterfalls e acelerar a renderização do servidor.
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥
Referência rápida para padrões de busca de dados paralelos.
// Promise.all - falha rapidamente se qualquer promise rejeitar
const [users, posts, stats] = await Promise.all([
getUsers(),
getPosts(),
getStats(),
]);
// Promise.allSettled - nunca rejeita, retorna status para cada uma
const results = await Promise.allSettled([
getUsers(),
getPosts(),
getStats(),
]);
// Promise.race - resolve/rejeita com a primeira a resolver/rejeitar
const fastest = await Promise.race([
fetchFromPrimary(),
fetchFromFallback(),
]);
// Promise.any - resolve com a primeira a cumprir (ignora rejeições)
const firstSuccess = await Promise.any([
fetchFromCDN1(),
fetchFromCDN2(),
]);Quando usar isso: Você tem múltiplas buscas de dados independentes em um Server Component e quer que elas rodem simultaneamente em vez de sequencialmente.
// app/dashboard/page.tsx
import { Suspense } from "react";
async function getUser(id: string) {
const res = await fetch(`https://api.example.com/users/${id}`);
return res.json();
}
async function getOrders(userId: string) {
const res = await fetch(`https://api.example.com/orders?userId=${userId}`);
return res.json();
}
async function getNotifications(userId: string) {
const res = await fetch(`https://api.example.com/notifications?userId=${userId}`);
return res.json();
}
export default async function DashboardPage() {
const userId = "user-123";
// Todas as três buscas começam ao mesmo tempo
const [user, orders, notifications] = await Promise.all([
getUser(userId),
getOrders(userId),
getNotifications(userId),
]);
return (
<div>
<h1>Bem-vindo, {user.name}</h1>
<p>{notifications.length} notificações não lidas</p>
<h2>Pedidos Recentes</h2>
<ul>
{orders.map((order: { id: string; total: number }) => (
<li key={order.id}>${order.total}</li>
))}
</ul>
</div>
);
}O que isso demonstra:
Promise.all recebe um array de promises e retorna uma única promise que resolve para um array de resultadosPromise.all rejeita imediatamente com esse errofetch do Next.js em Server Components é automaticamente deduped e cacheado| Método | Resolve Quando | Rejeita Quando | Retorna |
|---|---|---|---|
Promise.all | Todas cumprem | Qualquer uma rejeita | Array de valores |
Promise.allSettled | Todas resolvem/rejeitam | Nunca | Array de {status, value} ou {status, reason} |
Promise.race | A primeira a resolver/rejeitar | A primeira a rejeitar | Valor único |
Promise.any | A primeira a cumprir | Todas rejeitam | Valor único |
Promise.allSettled para falhas parciais:
export default async function DashboardPage() {
const results = await Promise.allSettled([
getUser("user-123"),
getOrders("user-123"),
getRecommendations("user-123"), // Pode falhar, não é crítico
]);
const user = results[0].status === "fulfilled" ? results[0].value : null;
const orders = results[1].status === "fulfilled" ? results[1].value : [];
const recs = results[2].status === "fulfilled" ? results[2].value : [];
return (
<div>
{user && <h1>{user.name}</h1>}
<OrderList orders={orders} />
{recs.length > 0 && <Recommendations items={recs} />}
</div>
);
}Buscas paralelas com Suspense (streaming independente):
// Cada seção carrega independentemente - não precisa de Promise.all
export default function DashboardPage() {
return (
<div>
<Suspense fallback={<Skeleton />}>
<UserProfile id="user-123" />
</Suspense>
<Suspense fallback={<Skeleton />}>
<OrderList userId="user-123" />
</Suspense>
<Suspense fallback={<Skeleton />}>
<Notifications userId="user-123" />
</Suspense>
</div>
);
}
// Cada componente busca seus próprios dados
async function UserProfile({ id }: { id: string }) {
const user = await getUser(id);
return <h1>{user.name}</h1>;
}Buscas dependentes (waterfall está correto):
// Quando a busca B depende da busca A, sequencial está correto
export default async function UserPage({ params }: { params: Promise<{ id: string }> }) {
const { id } = await params;
const user = await getUser(id);
// Estas dependem dos dados do usuário, mas são independentes entre si
const [posts, followers] = await Promise.all([
getPostsByAuthor(user.id),
getFollowers(user.id),
]);
return <Profile user={user} posts={posts} followers={followers} />;
}Função auxiliar para buscas paralelas tipadas:
async function fetchParallel<T extends readonly Promise<unknown>[]>(
...promises: T
): Promise<{ -readonly [K in keyof T]: Awaited<T[K]> }> {
return Promise.all(promises) as Promise<{ -readonly [K in keyof T]: Awaited<T[K]> }>;
}
// Uso - resultados totalmente tipados
const [user, posts] = await fetchParallel(
getUser("123"), // Tipo User
getPosts("123"), // Tipo Post[]
);// Promise.all preserva tipos de tupla
const results = await Promise.all([
getUser("1"), // retorna Promise<User>
getPosts("1"), // retorna Promise<Post[]>
getCount(), // retorna Promise<number>
]);
// results é [User, Post[], number]
// Tipo de resultado de Promise.allSettled
type SettledResult<T> =
| { status: "fulfilled"; value: T }
| { status: "rejected"; reason: unknown };
// Type guard para resultados resolvidos/rejeitados
function isFulfilled<T>(
result: PromiseSettledResult<T>
): result is PromiseFulfilledResult<T> {
return result.status === "fulfilled";
}
const results = await Promise.allSettled([getUsers(), getPosts()]);
const successfulResults = results.filter(isFulfilled).map(r => r.value);Promise.all falha rapidamente. Se uma promise rejeitar, você perde TODOS os resultados, mesmo daqueles que tiveram sucesso. Correção: Use Promise.allSettled quando algumas buscas não forem críticas, ou envolva promises individuais em try/catch.
Promises começam imediatamente, não quando aguardadas. const p = fetch(url) inicia a busca imediatamente. await apenas espera pelo resultado. Se você criar promises em um loop, todas elas começarão em paralelo. Correção: Geralmente é o que você quer, mas esteja ciente para APIs com limites de taxa.
Rejeição não tratada em Promise.all. Se você esquecer de capturar a rejeição de Promise.all, ela se torna uma rejeição de promise não tratada e pode travar seu servidor. Correção: Sempre envolva em try/catch ou use Promise.allSettled.
Não é realmente paralelo. Se você acidentalmente await cada busca antes de iniciar a próxima, elas rodam sequencialmente. Correção: Crie todas as promises primeiro, depois await elas juntas.
// Ruim: sequencial (cada await bloqueia)
const users = await getUsers();
const posts = await getPosts();
// Bom: paralelo (ambas começam imediatamente)
const [users, posts] = await Promise.all([getUsers(), getPosts()]);p-limit ou agrupe requisições.| Alternativa | Use Quando | Não Use Quando |
|---|---|---|
await Sequencial | Buscas dependem umas das outras (B precisa do resultado de A) | Buscas independentes (cria waterfalls) |
| Limites de Suspense | Cada seção deve fazer streaming independentemente | Você precisa de todos os dados antes de renderizar qualquer coisa |
Promise.allSettled | Algumas buscas são opcionais ou podem falhar | Todos os dados são necessários para renderizar |
Promise.race | Você quer a resposta mais rápida de múltiplas fontes | Você precisa de todos os resultados |
p-limit | Você precisa limitar a concorrência (APIs com limite de taxa) | Poucas buscas paralelas |
De uma aplicação SaaS em produção Next.js 15 / React 19 (SystemsArchitect.io).
// Exemplo de produção: API de documentos para administradores com 6 consultas Prisma paralelas
// Arquivo: app/api/admin/documents/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { prisma } from '@/lib/prisma';
export async function GET(request: NextRequest) {
const { searchParams } = request.nextUrl;
const page = parseInt(searchParams.get('page') ?? '1', 10);
const limit = parseInt(searchParams.get('limit') ?? '20', 10);
const offset = (page - 1) * limit;
const [
documents,
totalCount,
categoryCounts,
recentlyUpdated,
publishedCount,
storageUsed,
] = await Promise.all([
// Consulta principal com truque de paginação: busca limit+1 para detectar se há mais páginas
prisma.document.findMany({
take: limit + 1,
skip: offset,
orderBy: { updatedAt: 'desc' },
include: { author: { select: { name: true, email: true } } },
}),
prisma.document.count(),
prisma.document.groupBy({
by: ['categoryId'],
_count: { id: true },
}),
prisma.document.findMany({
take: 5,
orderBy: { updatedAt: 'desc' },
where: { updatedAt: { gte: new Date(Date.now() - 24 * 60 * 60 * 1000) } },
}),
prisma.document.count({ where: { status: 'PUBLISHED' } }),
// Consulta bruta específica do Postgres para agregação
prisma.$queryRaw<[{ total: bigint }]>`
SELECT COALESCE(SUM("file_size"), 0) as total FROM "Document"
`,
]);
const hasMore = documents.length > limit;
const paginatedDocs = hasMore ? documents.slice(0, limit) : documents;
const totalStorage = Number(storageUsed[0]?.total ?? 0n);
return NextResponse.json({
documents: paginatedDocs,
hasMore,
totalCount,
categoryCounts,
recentlyUpdated,
publishedCount,
totalStorage,
});
}O que isso demonstra em produção:
take: limit + 1 evita uma consulta de contagem separada apenas para saber se há uma próxima página. Se você receber mais linhas do que limit, há mais páginas. Corte a linha extra antes de retornar.$queryRaw é usado para a agregação COALESCE(SUM(...)) específica do Postgres porque a API de agregação do Prisma não suporta COALESCE. O tipo de retorno deve ser anotado explicitamente como [{ total: bigint }] porque o Postgres retorna bigint para SUM.bigint do Postgres não pode ser serializado diretamente para JSON. Number(storageUsed[0]?.total ?? 0n) o converte para um número regular. Para valores que podem exceder Number.MAX_SAFE_INTEGER, use .toString() em vez disso.Promise.all. O tempo total é a duração da consulta mais lenta, não a soma de todas as seis. Em um cold start, isso reduziu o endpoint de cerca de 1200ms (sequencial) para menos de 300ms.num_cpus * 2 + 1. Em plataformas serverless, mantenha a contagem de consultas paralelas dentro dos limites do pool ou aumente o tamanho do pool na string de conexão.Promise.all rejeita imediatamente com esse erroPromise.allSettled quando algumas buscas não forem críticasstatus de "fulfilled" ou "rejected" para que você possa lidar com cada um individualmenteawait bloqueia a próxima linhaconst a = await fetchA(); const b = await fetchB(); espera A terminar antes de criar Bawait Promise.all([...])// Ruim: sequencial
const users = await getUsers();
const posts = await getPosts();
// Bom: paralelo
const [users, posts] = await Promise.all([getUsers(), getPosts()]);Promise.race resolve ou rejeita com a primeira promise a resolver/rejeitar (seja cumprir ou rejeitar)Promise.any resolve com a primeira promise a cumprir, ignorando rejeiçõesPromise.any só rejeita se todas as promises rejeitarem (com um AggregateError)const results = await Promise.all([
getUser("1"), // Promise<User>
getPosts("1"), // Promise<Post[]>
getCount(), // Promise<number>
]);
// results é [User, Post[], number] -- totalmente tipadofunction isFulfilled<T>(
result: PromiseSettledResult<T>
): result is PromiseFulfilledResult<T> {
return result.status === "fulfilled";
}
const results = await Promise.allSettled([getUsers(), getPosts()]);
const successes = results.filter(isFulfilled).map((r) => r.value);Promise.all para sub-buscas independentesconst user = await getUser(id);
const [posts, followers] = await Promise.all([
getPostsByAuthor(user.id),
getFollowers(user.id),
]);num_cpus * 2 + 1)p-limit para agrupar requisiçõeslimit, há mais páginas (hasMore = true)COUNT(*) separada apenas para verificar a próxima página<Suspense> busca independentementePromise.all, as seções renderizam à medida que resolvem em vez de esperar que todas terminemPromise.all quando você precisa de todos os dados antes de renderizarbigint para agregações SUMbigint não pode ser serializado diretamente para JSON (gera TypeError)Number() o converte para um número regular; use .toString() se o valor puder exceder Number.MAX_SAFE_INTEGERasync function fetchParallel<T extends readonly Promise<unknown>[]>(
...promises: T
): Promise<{ -readonly [K in keyof T]: Awaited<T[K]> }> {
return Promise.all(promises) as any;
}Revisado por Chris St. John·Última atualização: 16 de jul. de 2026
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥