Add request retry backoff handling
This commit is contained in:
+51
-3
@@ -6,6 +6,8 @@ export const RETRYABLE_NETWORK_STATUS_CODES = [
|
|||||||
1, 408, 425, 429, 500, 502, 503, 504, 522, 524,
|
1, 408, 425, 429, 500, 502, 503, 504, 522, 524,
|
||||||
]
|
]
|
||||||
|
|
||||||
|
export const DEFAULT_BACKOFF_DELAY = 500
|
||||||
|
|
||||||
function isRetryableError(e: unknown) {
|
function isRetryableError(e: unknown) {
|
||||||
if (e instanceof XRPCError) {
|
if (e instanceof XRPCError) {
|
||||||
if (RETRYABLE_NETWORK_STATUS_CODES.includes(e.status)) {
|
if (RETRYABLE_NETWORK_STATUS_CODES.includes(e.status)) {
|
||||||
@@ -16,37 +18,83 @@ function isRetryableError(e: unknown) {
|
|||||||
return isNetworkError(e)
|
return isNetworkError(e)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function computeBackoff({
|
||||||
|
exception: e,
|
||||||
|
retryCount,
|
||||||
|
}: {
|
||||||
|
exception: any
|
||||||
|
retryCount: number
|
||||||
|
}) {
|
||||||
|
if (e instanceof XRPCError) {
|
||||||
|
if (e.status === 429) {
|
||||||
|
const retryAfter = parseInt(
|
||||||
|
e.headers?.['Retry-After'] || e.headers?.['retry-after'] || '',
|
||||||
|
)
|
||||||
|
if (typeof retryAfter === 'number' && !Number.isNaN(retryAfter)) {
|
||||||
|
return retryAfter * 1000
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return DEFAULT_BACKOFF_DELAY * Math.pow(2, retryCount)
|
||||||
|
}
|
||||||
|
|
||||||
export async function retry<P>(
|
export async function retry<P>(
|
||||||
fn: () => Promise<P>,
|
fn: () => Promise<P>,
|
||||||
{
|
{
|
||||||
retries,
|
retries: retriesRemaining,
|
||||||
checkIsRetryableError = isRetryableError,
|
checkIsRetryableError = isRetryableError,
|
||||||
|
backoff,
|
||||||
}: {
|
}: {
|
||||||
retries: number
|
retries: number
|
||||||
checkIsRetryableError?: (err: any) => boolean
|
checkIsRetryableError?: (err: any) => boolean
|
||||||
|
backoff?: boolean
|
||||||
},
|
},
|
||||||
): Promise<P> {
|
): Promise<P> {
|
||||||
let lastErr
|
let lastErr
|
||||||
while (retries > 0) {
|
let retryCount = 0
|
||||||
|
let backoffDelay = DEFAULT_BACKOFF_DELAY
|
||||||
|
|
||||||
|
while (retriesRemaining > 0) {
|
||||||
try {
|
try {
|
||||||
|
if (retryCount > 0 && backoff) {
|
||||||
|
await sleep(backoffDelay)
|
||||||
|
}
|
||||||
|
|
||||||
return await fn()
|
return await fn()
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
lastErr = e
|
lastErr = e
|
||||||
|
|
||||||
if (checkIsRetryableError(e)) {
|
if (checkIsRetryableError(e)) {
|
||||||
retries--
|
backoffDelay = computeBackoff({
|
||||||
|
exception: e,
|
||||||
|
retryCount: retryCount,
|
||||||
|
})
|
||||||
|
retryCount++
|
||||||
|
retriesRemaining--
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
throw e
|
throw e
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
throw lastErr
|
throw lastErr
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function networkRetry<P>(
|
export async function networkRetry<P>(
|
||||||
retries: number,
|
retries: number,
|
||||||
fn: () => Promise<P>,
|
fn: () => Promise<P>,
|
||||||
|
options: {
|
||||||
|
backoff?: boolean
|
||||||
|
},
|
||||||
): Promise<P> {
|
): Promise<P> {
|
||||||
return retry(fn, {
|
return retry(fn, {
|
||||||
retries,
|
retries,
|
||||||
|
backoff: options.backoff,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function sleep(time: number) {
|
||||||
|
return new Promise(y => setTimeout(y, time))
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user