karawaci.kode

← Semua snippet

JavaScript Menengah Utility

Async retry dengan exponential backoff (vanilla JS)

Wrap async function dengan retry logic. Exponential backoff. Configurable max attempts, base delay, jitter. Zero dependencies.

Dipublikasikan 2 Juni 2026

Setiap kali fetch API external (payment gateway, social, AI), ada chance dia balas 5xx atau timeout. Bukan masalah app, masalah network/server lain. Retry dengan exponential backoff = self-healing tanpa user terganggu.

Kode

/**
 * Retry async function dengan exponential backoff + jitter.
 * @param {Function} fn - Async function to retry.
 * @param {object} [opts]
 * @param {number} [opts.maxAttempts=3] - Maximum total attempts.
 * @param {number} [opts.baseDelay=300] - Delay first retry (ms).
 * @param {number} [opts.maxDelay=10000] - Cap delay.
 * @param {Function} [opts.shouldRetry] - Predicate (error) => bool.
 * @returns {Promise<any>}
 */
async function retry(fn, opts = {}) {
  const {
    maxAttempts = 3,
    baseDelay = 300,
    maxDelay = 10000,
    shouldRetry = (err) => true,
  } = opts;

  let attempt = 0;
  let lastError;

  while (attempt < maxAttempts) {
    attempt++;
    try {
      return await fn(attempt);
    } catch (err) {
      lastError = err;
      
      if (attempt >= maxAttempts || !shouldRetry(err)) {
        throw err;
      }

      const delay = Math.min(maxDelay, baseDelay * Math.pow(2, attempt - 1));
      const jitter = delay * 0.2 * Math.random();
      const waitMs = delay + jitter;
      
      console.warn(`[retry] attempt ${attempt} failed: ${err.message}. Retry in ${Math.round(waitMs)}ms`);
      await new Promise((r) => setTimeout(r, waitMs));
    }
  }

  throw lastError;
}

Pemakaian

// Retry HTTP request
const result = await retry(
  async () => {
    const res = await fetch('https://api.example.com/data');
    if (!res.ok) throw new Error(`HTTP ${res.status}`);
    return res.json();
  },
  {
    maxAttempts: 5,
    baseDelay: 500,
    shouldRetry: (err) => {
      // Hanya retry untuk network error & 5xx
      const msg = err.message;
      return msg.includes('fetch') || msg.match(/HTTP 5\d\d/);
    },
  }
);

Kapan dipakai

  • Fetch ke external API (payment gateway, social, weather)
  • DB connection awal (saat app start, DB belum ready)
  • Webhook delivery dari worker ke external
  • File upload ke S3/R2 dengan timeout

Kapan JANGAN retry

  • 400 Bad Request — kemungkinan body Anda salah, retry sama hasil
  • 401 Unauthorized — token expired, butuh refresh dulu bukan retry
  • 404 Not Found — resource gak ada
  • 422 Unprocessable Entity — validation error
  • Mutation idempotency tidak guaranteed — kalau POST tanpa idempotency key, retry bisa duplicate transaction

Atur shouldRetry predicate untuk filter case-case ini.

Catatan

  • Jitter (random 20% tambahan) penting untuk avoid thundering herd kalau banyak client retry bersamaan
  • Max delay cap prevent waiting too long (jangan tunggu 5 menit)
  • Console warn helpful saat debug; di production switch ke proper logger

Untuk production-grade: pakai library seperti p-retry atau axios-retry yang sudah handle edge case. Snippet ini untuk understanding atau minimal-dep usage.

# tags

retryasyncresiliencebackoff

Ditulis oleh Asti Larasati · 2 Juni 2026