diff --git a/frontend/src/ts/ape/utils.ts b/frontend/src/ts/ape/utils.ts new file mode 100644 index 000000000..34ca02500 --- /dev/null +++ b/frontend/src/ts/ape/utils.ts @@ -0,0 +1,53 @@ +type ShouldRetryCallback = ( + statusCode: number, + response?: Ape.HttpClientResponse +) => boolean; + +interface RetryOptions { + shouldRetry?: ShouldRetryCallback; + retryAttempts?: number; + retryDelayMs?: number; +} + +const wait = (delay: number): Promise => + new Promise((resolve) => window.setTimeout(resolve, delay)); + +const DEFAULT_RETRY_OPTIONS: Required = { + shouldRetry: (statusCode: number): boolean => + statusCode >= 500 && statusCode !== 503, + retryAttempts: 3, + retryDelayMs: 3000, +}; + +export async function withRetry( + fn: () => Ape.EndpointData, + opts?: RetryOptions +): Ape.EndpointData { + const retry = async ( + previousData: Ape.HttpClientResponse, + completeOpts: Required + ): Promise => { + const { retryAttempts, shouldRetry, retryDelayMs } = completeOpts; + + if (retryAttempts <= 0 || !shouldRetry(previousData.status, previousData)) { + return previousData; + } + + const data = await fn(); + const { status } = data; + + if (shouldRetry(status, data)) { + await wait(retryDelayMs); + + --completeOpts.retryAttempts; + return await retry(data, completeOpts); + } + + return data; + }; + + return await retry(await fn(), { + ...DEFAULT_RETRY_OPTIONS, + ...opts, + }); +}