Retry & Outbound Resilience
Chamadas de saída do Node falham transitoriamente. Combine timeouts, retentativas com backoff e circuit breakers para que um fornecedor lento não paralise sua API.
Busque em todas as páginas da documentação
Chamadas de saída do Node falham transitoriamente. Combine timeouts, retentativas com backoff e circuit breakers para que um fornecedor lento não paralise sua API.
Cartão de receita de referência rápida - pronto para copiar e colar.
import got from "got";
const client = got.extend({
timeout: { request: 8_000 },
retry: {
limit: 3,
methods: ["GET", "PUT", "HEAD", "DELETE", "OPTIONS", "TRACE"],
statusCodes: [408, 413, 429, 500, 502, 503, 504],
calculateDelay: ({ attemptCount }) => Math.min(1000 * 2 ** attemptCount, 10_000) + Math.random() * 200,
},
hooks: {
beforeRetry: [(err, retryCount) => {
console.warn({ msg: "got_retry", retryCount, err: err.message });
}],
},
});Quando usar isso:
// src/http/partner-client.ts
import got, { type Got } from "got";
function buildClient(): Got {
return got.extend({
prefixUrl: process.env.PARTNER_API_URL,
headers: { Authorization: `Bearer ${process.env.PARTNER_TOKEN}` },
timeout: { request: 8_000 },
retry: {
limit: 3,
methods: ["GET"],
statusCodes: [429, 500, 502, 503, 504],
calculateDelay: ({ attemptCount }) => 500 * 2 ** attemptCount + Math.random() * 100,
},
});
}
export const partnerClient = buildClient();
// POST com retentativa idempotente manual
export async function createPartnerJob(idempotencyKey: string, body: unknown) {
const maxAttempts = 3;
let attempt = 0;
while (attempt < maxAttempts) {
try {
return await partnerClient.post("jobs", {
json: body,
headers: { "Idempotency-Key": idempotencyKey },
retry: { limit: 0 },
}).json();
} catch (err) {
attempt++;
if (!isRetryable(err) || attempt >= maxAttempts) throw err;
await sleep(500 * 2 ** attempt);
}
}
}
function isRetryable(err: unknown): boolean {
if (!got.isHTTPError(err)) return true; // rede
const code = err.response?.statusCode ?? 0;
return code >= 500 || code === 429;
}O que isso demonstra:
import axios from "axios";
import axiosRetry from "axios-retry";
const api = axios.create({ baseURL: process.env.PARTNER_API_URL, timeout: 8000 });
axiosRetry(api, {
retries: 3,
retryDelay: axiosRetry.exponentialDelay,
retryCondition: (err) => axiosRetry.isNetworkOrIdempotentRequestError(err) || err.response?.status === 429,
});axios-retry espelha a política do got| Status | Tentar novamente? |
|---|---|
| 400 Bad Request | Não - corrigir payload |
| 401/403 | Não - corrigir credenciais |
| 404 | Não - recurso errado |
| 409 Conflict | Não - a menos que upsert idempotente |
| 429 | Sim com backoff respeitando Retry-After |
| 503 | Sim, tentativas limitadas |
import CircuitBreaker from "opossum";
const breaker = new CircuitBreaker(callPartner, {
timeout: 10_000,
errorThresholdPercentage: 50,
resetTimeout: 30_000,
});
breaker.fallback(() => ({ status: "degraded" }));const deadline = Date.now() + 15_000;
while (attempts--) {
const remaining = deadline - Date.now();
if (remaining <= 0) throw new Error("deadline_exceeded");
await client.get("x", { timeout: { request: remaining } });
}Retry-After.| Alternativa | Usar Quando | Não Usar Quando |
|---|---|---|
| Fila de retentativa apenas | Usuário não está esperando em um caminho síncrono | Usuário precisa de resposta imediata do parceiro |
| Falhar rápido, sem retentativa | Enriquecimento opcional | Autorização de pagamento |
| Retentativa de atividade Temporal | Orquestração de fluxo de trabalho longa | Fetch simples |
| Requisições Hedged | Latência de cauda extrema | Apenas leitura idempotente, dobra a carga |
Ambos mantidos. got é nativo ESM; axios é ubíquo. Escolha um por repositório.
Máximo de 1-2 retentativas rápidas dentro da requisição do usuário. Retentativas pesadas pertencem ao worker.
Stripe retenta solicitações idempotentes automaticamente - não envolva cegamente.
Conta como erro de rede - retente com backoff; alerte se persistente.
Cada tentativa precisa de timeout; o relógio total também é limitado.
Seguro para retentar se a chave do recurso for estável. POST create não é sem chave.
MSW/nock retornam 503 duas vezes e depois 200; afirme a contagem de chamadas e o sucesso final.
Fetch nativo não tem retentativa - envolva com got/axios ou um helper personalizado.
Operador retry do RxJS - as mesmas regras de idempotência se aplicam.
Evite retentativas globais implícitas - instâncias de cliente por integração são mais claras.
Versões da Stack: Esta página foi escrita para Node.js 24.18.0 (Active LTS), npm 10+, TypeScript 5.6+, Express 5, Fastify 5 e NestJS 11.
Revisado por Chris St. John·Última atualização: 16 de jul. de 2026