Classifique o erro antes de decidir a ação:
| Classe | Status | Ação |
|---|---|---|
| Erro do cliente | 4xx (exceto 429) | Corrigir a requisição; não retentar sem mudança |
| Rate limit | 429 | Aguardar reset e retentar |
| Erro de servidor | 5xx | Retentar com backoff exponencial |
| Conflito de estado | 409 | Verificar estado atual antes de retentar |
Erros 4xx indicam problema na sua requisição. Não faça retry sem corrigir a causa.
async function chamarApi(url: string, options: RequestInit) {
const res = await fetch(url, options);
if (res.status >= 400 && res.status < 500 && res.status !== 429) {
const erro = await res.json();
// Logar código de erro para diagnóstico
console.error(`Erro ${res.status}: ${erro.erro} — ${erro.mensagem}`);
throw new Error(`Erro de cliente: ${erro.erro}`);
}
return res;
}async function chamarComBackoff(
fn: () => Promise<Response>,
maxTentativas = 4
): Promise<Response> {
for (let i = 0; i < maxTentativas; i++) {
const res = await fn();
if (res.status !== 429) return res;
const resetHeader = res.headers.get('X-RateLimit-Reset');
const aguardar = resetHeader
? Math.max(Number(resetHeader) * 1000 - Date.now(), 0)
: Math.pow(2, i) * 1000;
console.warn(`Rate limit atingido. Aguardando ${aguardar}ms...`);
await new Promise(r => setTimeout(r, aguardar));
}
throw new Error('Rate limit excedido após todas as tentativas');
}async function chamarComRetry(
fn: () => Promise<Response>,
maxTentativas = 3
): Promise<Response> {
const intervalos = [1000, 5000, 30000];
for (let i = 0; i < maxTentativas; i++) {
try {
const res = await fn();
if (res.status < 500) return res;
console.warn(`Erro ${res.status} na tentativa ${i + 1}`);
} catch (e) {
console.warn(`Falha de rede na tentativa ${i + 1}:`, e);
}
if (i < maxTentativas - 1) {
await new Promise(r => setTimeout(r, intervalos[i]));
}
}
throw new Error('Serviço indisponível após retries');
}Idempotência no retry
Erros 5xx não consomem a idempotencyKey. Reenvie a mesma chave no retry — é seguro e evita duplicatas.
Um 409 Conflict em confirmação indica que o pedido já mudou de estado. Verifique o estado atual antes de retentar:
async function confirmarComVerificacao(pedidoId: string, key: string) {
const res = await fetch(`${BASE_URL}/pedidos/${pedidoId}/confirmar`, {
method: 'POST',
headers: headers,
body: JSON.stringify({ idempotencyKey: key }),
});
if (res.status === 409) {
// Verificar estado atual
const estadoRes = await fetch(`${BASE_URL}/pedidos/${pedidoId}`, { headers });
const pedido = await estadoRes.json();
console.log(`Pedido já em estado: ${pedido.status}`);
// Se já confirmado, operação foi bem-sucedida — não é erro
if (pedido.status === 'confirmado') return pedido;
throw new Error(`Conflito inesperado: ${pedido.status}`);
}
return res.json();
}- Todos os erros logam o campo
erro(código de aplicação), não só o status HTTP. - Retry implementado com backoff exponencial para 5xx e 429.
-
idempotencyKeyreutilizada nos retries de 5xx. - Erros 4xx lançam exceção sem retry (exceto 429).
- Conflitos 409 verificam estado atual antes de agir.
- Timeout de conexão configurado (recomendado: 10s).
- Guia de erros — catálogo completo de códigos
- Guia de idempotência
- Guia de rate limits