mirror of
https://github.com/Sendouc/sendou.ink.git
synced 2026-08-20 01:55:26 -05:00
Add retry to fetch to mitigate transient network errors
This commit is contained in:
@@ -34,6 +34,11 @@ if (SENTRY_ENABLED) {
|
||||
});
|
||||
}
|
||||
|
||||
/** Base delays in milliseconds before each retry attempt following the initial request. */
|
||||
const FETCH_RETRY_DELAYS_MS = [0, 5000, 15000];
|
||||
/** Random jitter added to each retry delay to avoid a thundering herd against a struggling server. */
|
||||
const FETCH_RETRY_JITTER_MS = 1000;
|
||||
|
||||
const originalFetch = window.fetch;
|
||||
window.fetch = (input, init) => {
|
||||
const url =
|
||||
@@ -53,9 +58,66 @@ window.fetch = (input, init) => {
|
||||
const sessionId = getSessionId();
|
||||
const headers = new Headers(init?.headers);
|
||||
headers.set("Sendou-Session-Id", sessionId);
|
||||
return originalFetch(input, { ...init, headers });
|
||||
|
||||
return fetchWithRetry(input, { ...init, headers });
|
||||
};
|
||||
|
||||
const isRetryableMethod = (method: string) => {
|
||||
const normalized = method.toUpperCase();
|
||||
return normalized === "GET" || normalized === "HEAD";
|
||||
};
|
||||
|
||||
const wait = (ms: number) =>
|
||||
new Promise<void>((resolve) => setTimeout(resolve, ms));
|
||||
|
||||
async function fetchWithRetry(
|
||||
input: RequestInfo | URL,
|
||||
init: RequestInit,
|
||||
): Promise<Response> {
|
||||
const method =
|
||||
init.method ?? (input instanceof Request ? input.method : "GET");
|
||||
|
||||
if (!isRetryableMethod(method)) {
|
||||
return originalFetch(input, init);
|
||||
}
|
||||
|
||||
let lastError: unknown;
|
||||
for (let attempt = 0; attempt <= FETCH_RETRY_DELAYS_MS.length; attempt++) {
|
||||
if (attempt > 0) {
|
||||
await wait(
|
||||
FETCH_RETRY_DELAYS_MS[attempt - 1] +
|
||||
Math.random() * FETCH_RETRY_JITTER_MS,
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await originalFetch(input, init);
|
||||
|
||||
// retry on server errors, but let the caller handle other statuses
|
||||
if (response.status >= 500 && attempt < FETCH_RETRY_DELAYS_MS.length) {
|
||||
continue;
|
||||
}
|
||||
|
||||
return response;
|
||||
} catch (error) {
|
||||
// a cancelled request (e.g. a superseded navigation) must fail fast, not retry
|
||||
if (
|
||||
init.signal?.aborted ||
|
||||
(error instanceof DOMException && error.name === "AbortError")
|
||||
) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
lastError = error;
|
||||
if (attempt === FETCH_RETRY_DELAYS_MS.length) {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
throw lastError;
|
||||
}
|
||||
|
||||
if ("serviceWorker" in navigator) {
|
||||
window.addEventListener("load", () => {
|
||||
// we will register it after the page complete the load
|
||||
|
||||
Reference in New Issue
Block a user