Como repetir requisições com backoff exponencial (retry)
Chamadas de rede falham de vez em quando: o servidor fica sobrecarregado, a conexão cai, a API pede para esperar. Repetir na hora piora o problema. Este guia mostra como repetir com backoff exponencial e jitter, quais respostas vale repetir (429 e 5xx, mas não 404) e como evitar que uma nova tentativa faça a mesma cobrança duas vezes.
Por que esperar cada vez mais
Se mil clientes repetem a chamada na mesma hora em que o servidor caiu, ele volta e cai de novo. O backoff exponencial dobra a espera a cada tentativa (200 ms, 400 ms, 800 ms...), com um teto. O jitter sorteia a espera entre zero e esse limite, para os clientes não voltarem todos juntos.
O que vale repetir
- Falhas de rede e tempo esgotado: podem passar na próxima tentativa.
- 429 (muitas requisições) e 503 (indisponível): a API pede para esperar. Se a resposta trouxer o cabeçalho
Retry-After, espere o tempo indicado. - Outros 5xx: costumam ser temporários.
- Não repita 400, 401, 403, 404 e 422: a resposta não muda enquanto o pedido for o mesmo, e repetir só gasta tempo e cota.
Em JavaScript
A função comRetry serve para qualquer tarefa assíncrona, e buscarComRetry aplica as regras acima ao fetch. No fetch, uma falha de rede aparece como TypeError, enquanto respostas de erro, como 503, chegam normalmente e precisam ser conferidas pelo status.
const esperar = (ms) => new Promise((ok) => setTimeout(ok, ms));
// Repete a tarefa quando ela falha, esperando cada vez mais entre as tentativas
async function comRetry(tarefa, opcoes = {}) {
const { tentativas = 5, baseMs = 200, maxMs = 5000, repetirSe = () => true } = opcoes;
for (let i = 0; ; i++) {
try {
return await tarefa();
} catch (erro) {
if (i + 1 >= tentativas || !repetirSe(erro)) throw erro;
// Backoff exponencial com jitter: espera um tempo aleatório entre 0 e o limite da vez
const limite = Math.min(maxMs, baseMs * 2 ** i);
await esperar(Math.random() * limite);
}
}
}
// Só vale repetir quando o problema pode passar: 429 (muitas requisições) e erros 5xx
async function buscarComRetry(url, opcoes) {
return comRetry(async () => {
const resposta = await fetch(url, opcoes);
if (resposta.status === 429 || resposta.status >= 500) {
throw Object.assign(new Error(`HTTP ${resposta.status}`), { temporario: true });
}
return resposta; // outros 4xx não mudam com nova tentativa
}, { repetirSe: (e) => e.temporario || e.name === 'TypeError' }); // TypeError: falha de rede
} Em Python e PHP
O repetir_se recebe o erro e decide se vale tentar de novo. Em Python, bibliotecas como tenacity e o Retry do urllib3 já fazem isso com mais opções.
import random
import time
def com_retry(tarefa, tentativas=5, base=0.2, maximo=5.0, repetir_se=lambda erro: True):
"""Repete a tarefa quando ela falha, esperando cada vez mais entre as tentativas."""
for i in range(tentativas):
try:
return tarefa()
except Exception as erro:
if i + 1 == tentativas or not repetir_se(erro):
raise
# Backoff exponencial com jitter: entre 0 e o limite da vez
time.sleep(random.uniform(0, min(maximo, base * 2**i))) <?php
// Repete a tarefa quando ela lança exceção, esperando cada vez mais entre as tentativas
function comRetry(
callable $tarefa,
int $tentativas = 5,
int $baseMs = 200,
int $maxMs = 5000,
?callable $repetirSe = null
) {
for ($i = 0; ; $i++) {
try {
return $tarefa();
} catch (Throwable $erro) {
if ($i + 1 >= $tentativas || ($repetirSe && !$repetirSe($erro))) {
throw $erro;
}
$limite = min($maxMs, $baseMs * 2 ** $i);
usleep(random_int(0, $limite) * 1000); // jitter: entre 0 e o limite da vez
}
}
} Não cobre duas vezes
Uma requisição pode chegar ao servidor e a resposta se perder no caminho. Se você repetir uma criação de pedido ou um pagamento, pode fazer tudo de novo. Repita sem medo só operações idempotentes, que dão o mesmo resultado se feitas duas vezes, como um GET ou um PUT que grava o mesmo valor.
- Para criações e pagamentos, envie uma chave de idempotência: um identificador único por operação, no cabeçalho
Idempotency-Key, que muitas APIs de pagamento aceitam. O servidor reconhece a repetição e devolve o resultado da primeira vez. - Gere essa chave uma vez, antes da primeira tentativa, por exemplo com um UUID, e mande a mesma em todas as repetições.
- Limite o tempo total, não só o número de tentativas: o usuário não deve esperar um minuto por uma resposta.
Achou um erro neste guia? Avise por e-mail.
Perguntas frequentes
O que é backoff exponencial?
É dobrar o tempo de espera a cada nova tentativa, como 200 ms, 400 ms, 800 ms, até um limite. Assim o cliente insiste sem sobrecarregar um servidor que já está com problema.
Para que serve o jitter?
Para os clientes não tentarem todos ao mesmo tempo. Em vez de esperar exatamente o limite, cada um sorteia um tempo entre zero e o limite, e as novas tentativas se espalham.
Devo repetir uma requisição que deu 404?
Não. Erros 4xx, exceto o 429, indicam que o pedido em si está errado ou não é permitido, e a resposta será a mesma. Repita falhas de rede, 429 e 5xx.
Como evitar cobrança duplicada ao repetir um pagamento?
Envie uma chave de idempotência, gerada uma vez antes da primeira tentativa e repetida em todas. A API de pagamento reconhece a repetição e não cobra de novo.
Outros guias
- Como buscar o endereço pelo CEP com o ViaCEPComo consultar o endereço de um CEP no ViaCEP em JavaScript, Python e PHP, tratar CEP inexistente e erros de rede e preencher o formulário sozinho.
- Como gerar PIX copia e cola (BR Code) em códigoComo montar o código PIX copia e cola (BR Code estático) em JavaScript, Python e PHP: os campos, o CRC16 e os erros que fazem o banco recusar o código.