A browser module retries a failed API call, shows each waiting state, and renders the result after the third response succeeds.

Program

The source loops through a small retry budget. Failed responses update the attempt list and wait before the next request; the successful response supplies the data for the final UI state.

fetch_retry_backoff.js
const panel = document.querySelector('#sync-panel');
const attempts = document.querySelectorAll('.attempt');
const result = document.querySelector('#sync-result');

const wait = (ms) => new Promise((resolve) => setTimeout(resolve, ms));

let report;

for (let attempt = 1; attempt <= 3; attempt += 1) {
  const response = await fetch('/api/sync');

  if (response.ok) {
    report = await response.json();
    attempts[attempt - 1].dataset.state = 'ok';
    attempts[attempt - 1].textContent = 'success';
    break;
  }

  attempts[attempt - 1].dataset.state = 'retry';
  attempts[attempt - 1].textContent = `retry in ${attempt * 250}ms`;
  await wait(attempt * 250);
}

panel.dataset.state = 'ready';
result.textContent = `${report.items} items synced`;
retry budget A retry loop should have a fixed limit so the browser does not keep requesting forever.
backoff delay Backoff gives the service time to recover before the next request. This example shows the delay value in the UI.
response sequence Each response changes what the next step can do: failed responses keep the loop moving, while a successful response breaks out.
final render The page should update only after the successful response has been parsed into usable data.