I am using RTK Query to perform my API calls. I have a mutation which I want to retry fetching if got the an error response. I also want to control the delay between the retries.
** Note: I tried maxRetries, but I have no control over the delay between retries
** Note 2: after digging deep, I found the "retryCondition" fn under "extraOptions". Also, I checked the "backoff". but I am not sure how to use these together to control the delay. I searched over the internet && found nothing about backoff Also the documentation mentions the "retry" briefly.
You're right, the docs don't surface this feature well enough and I had to go spelunking in the repo to find the information. The retry
function takes 2 arguments, first is the base query function and second is a default options (RetryOptions
) configuration object.
See RetryOptions
type declaration:
export type RetryOptions = { /** * Function used to determine delay between retries */ backoff?: (attempt: number, maxRetries: number) => Promise<void> } & ( | { /** * How many times the query will be retried (default: 5) */ maxRetries?: number retryCondition?: undefined } | { /** * Callback to determine if a retry should be attempted. * Return `true` for another retry and `false` to quit trying prematurely. */ retryCondition?: RetryConditionFunction maxRetries?: undefined } )
The backoff
property is what you're interested in
(attempt: number, maxRetries: number) => Promise<void>
It's a function that takes attempt and max retries arguments and returns a Promise. Fortunately there's already a default backoff function you can reference:
/** * Exponential backoff based on the attempt number. * * @remarks * 1. 600ms * random(0.4, 1.4) * 2. 1200ms * random(0.4, 1.4) * 3. 2400ms * random(0.4, 1.4) * 4. 4800ms * random(0.4, 1.4) * 5. 9600ms * random(0.4, 1.4) * * @param attempt - Current attempt * @param maxRetries - Maximum number of retries */ async function defaultBackoff(attempt: number = 0, maxRetries: number = 5) { const attempts = Math.min(attempt, maxRetries) const timeout = ~~((Math.random() + 0.4) * (300 << attempts)) // Force a positive int in the case we make this an option await new Promise((resolve) => setTimeout((res: any) => resolve(res), timeout) ) }
IMO this is a pretty good default function, but you could construct your own if necessary.
Examples:
1 second, 2 seconds, 3 seconds, ...maxRetries seconds
const customBackOff = async (attempt = 0, maxRetries = 5) => {
const attempts = Math.min(attempt, maxRetries);
await new Promise(resolve => {
setTimeout(resolve, attempts * 1000);
});
};
1 second each retry
const customBackOff = async (attempt = 0, maxRetries = 5) => {
await new Promise(resolve => {
setTimeout(resolve, 1000);
});
};
Lookup in array.
const backoffDelays = [500, 2000, 5000];
const customBackOff = async (attempt = 0, maxRetries = 5) => {
const attempts = Math.min(backoffDelays.length - 1, maxRetries);
await new Promise(resolve => {
setTimeout(resolve, backoffDelays[attempts]);
});
};
Follow the example from the documentation to decorate your base query function.
const customBaseQuery = retry(
fetchBaseQuery({ baseUrl: '/' }),
{ backoff: customBackOff, maxRetries: 5 }
);
export const api = createApi({
baseQuery: customBaseQuery,
endpoints: (build) => ({
....
}),
});