Skip to content

Commit e2ff14e

Browse files
committed
Make maxRetries and retryCondition mutually exclusive
1 parent 30d402e commit e2ff14e

File tree

2 files changed

+77
-32
lines changed

2 files changed

+77
-32
lines changed

packages/toolkit/src/query/retry.ts

Lines changed: 63 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,10 @@
1-
import type { BaseQueryApi, BaseQueryArg, BaseQueryEnhancer, BaseQueryExtraOptions, BaseQueryFn } from './baseQueryTypes'
1+
import type {
2+
BaseQueryApi,
3+
BaseQueryArg,
4+
BaseQueryEnhancer,
5+
BaseQueryExtraOptions,
6+
BaseQueryFn,
7+
} from './baseQueryTypes'
28
import { FetchBaseQueryError } from './fetchBaseQuery'
39
import { HandledError } from './HandledError'
410

@@ -24,42 +30,71 @@ async function defaultBackoff(attempt: number = 0, maxRetries: number = 5) {
2430
)
2531
}
2632

27-
export interface RetryOptions {
28-
/**
29-
* How many times the query will be retried (default: 5)
30-
*/
31-
maxRetries?: number
33+
type RetryConditionFunction = (
34+
error: FetchBaseQueryError,
35+
args: BaseQueryArg<BaseQueryFn>,
36+
extraArgs: {
37+
attempt: number
38+
baseQueryApi: BaseQueryApi
39+
extraOptions: BaseQueryExtraOptions<BaseQueryFn> & RetryOptions
40+
}
41+
) => boolean
42+
43+
export type RetryOptions = {
3244
/**
3345
* Function used to determine delay between retries
3446
*/
3547
backoff?: (attempt: number, maxRetries: number) => Promise<void>
36-
/**
37-
* Callback to determine if a retry should be attempted.
38-
* Return `true` for another retry and `false` to quit trying prematurely.
39-
*/
40-
retryCondition?: (error: FetchBaseQueryError, args: BaseQueryArg<BaseQueryFn>, extraArgs: {
41-
attempt: number
42-
maxRetries: number
43-
baseQueryApi: BaseQueryApi
44-
extraOptions: BaseQueryExtraOptions<BaseQueryFn> & RetryOptions
45-
}) => boolean
46-
}
48+
} & (
49+
| {
50+
/**
51+
* How many times the query will be retried (default: 5)
52+
*/
53+
maxRetries?: number
54+
retryCondition?: undefined
55+
}
56+
| {
57+
/**
58+
* Callback to determine if a retry should be attempted.
59+
* Return `true` for another retry and `false` to quit trying prematurely.
60+
*/
61+
retryCondition?: RetryConditionFunction
62+
maxRetries?: undefined
63+
}
64+
)
4765

4866
function fail(e: any): never {
4967
throw Object.assign(new HandledError({ error: e }), {
5068
throwImmediately: true,
5169
})
5270
}
5371

72+
const EMPTY_OPTIONS = {}
73+
5474
const retryWithBackoff: BaseQueryEnhancer<
5575
unknown,
5676
RetryOptions,
5777
RetryOptions | void
5878
> = (baseQuery, defaultOptions) => async (args, api, extraOptions) => {
59-
const defaultRetryCondition: Exclude<RetryOptions['retryCondition'], undefined> = (_, __, {attempt, maxRetries}) => attempt <= maxRetries
79+
// We need to figure out `maxRetries` before we define `defaultRetryCondition.
80+
// This is probably goofy, but ought to work.
81+
// Put our defaults in one array, filter out undefineds, grab the last value.
82+
const possibleMaxRetries: number[] = [
83+
5,
84+
((defaultOptions as any) || EMPTY_OPTIONS).maxRetries,
85+
((extraOptions as any) || EMPTY_OPTIONS).maxRetries,
86+
].filter(Boolean)
87+
const [maxRetries] = possibleMaxRetries.slice(-1)
6088

61-
const options = {
62-
maxRetries: 5,
89+
const defaultRetryCondition: RetryConditionFunction = (_, __, { attempt }) =>
90+
attempt <= maxRetries
91+
92+
const options: {
93+
maxRetries: number
94+
backoff: typeof defaultBackoff
95+
retryCondition: typeof defaultRetryCondition
96+
} = {
97+
maxRetries,
6398
backoff: defaultBackoff,
6499
retryCondition: defaultRetryCondition,
65100
...defaultOptions,
@@ -87,12 +122,14 @@ const retryWithBackoff: BaseQueryEnhancer<
87122
throw e
88123
}
89124

90-
if (e instanceof HandledError && !options.retryCondition(e.value as FetchBaseQueryError, args, {
91-
attempt: retry,
92-
maxRetries: options.maxRetries,
93-
baseQueryApi: api,
94-
extraOptions
95-
})) {
125+
if (
126+
e instanceof HandledError &&
127+
!options.retryCondition(e.value as FetchBaseQueryError, args, {
128+
attempt: retry,
129+
baseQueryApi: api,
130+
extraOptions,
131+
})
132+
) {
96133
return e.value
97134
}
98135
await options.backoff(retry, options.maxRetries)

packages/toolkit/src/query/tests/retry.test.ts

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import type { BaseQueryFn } from '@reduxjs/toolkit/query'
22
import { createApi, retry } from '@reduxjs/toolkit/query'
33
import { setupApiStore, waitMs } from './helpers'
4+
import type { RetryOptions } from '../retry'
45

56
beforeEach(() => {
67
jest.useFakeTimers('legacy')
@@ -350,8 +351,7 @@ describe('configuration', () => {
350351
const overrideMaxRetries = 3
351352

352353
const baseQuery = retry(baseBaseQuery, {
353-
maxRetries: 0,
354-
retryCondition: (_, __, {attempt}) => attempt <= overrideMaxRetries,
354+
retryCondition: (_, __, { attempt }) => attempt <= overrideMaxRetries,
355355
})
356356
const api = createApi({
357357
baseQuery,
@@ -380,16 +380,16 @@ describe('configuration', () => {
380380
baseBaseQuery.mockResolvedValue({ error: 'rejected' })
381381

382382
const baseQuery = retry(baseBaseQuery, {
383-
maxRetries: 10
383+
maxRetries: 10,
384384
})
385385
const api = createApi({
386386
baseQuery,
387387
endpoints: (build) => ({
388388
q1: build.query({
389389
query: () => {},
390390
extraOptions: {
391-
retryCondition: (_, __, {attempt, maxRetries}) => attempt <= maxRetries / 2,
392-
}
391+
retryCondition: (_, __, { attempt }) => attempt <= 5,
392+
},
393393
}),
394394
}),
395395
})
@@ -424,7 +424,7 @@ describe('configuration', () => {
424424
query: () => ({ method: 'PUT' }),
425425
extraOptions: {
426426
retryCondition: (e) => e.data === 'hello retryCondition',
427-
}
427+
},
428428
}),
429429
}),
430430
})
@@ -438,4 +438,12 @@ describe('configuration', () => {
438438

439439
expect(baseBaseQuery).toHaveBeenCalledTimes(4)
440440
})
441+
442+
test.skip('RetryOptions only accepts one of maxRetries or retryCondition', () => {
443+
// @ts-expect-error Should complain if both exist at once
444+
const ro: RetryOptions = {
445+
maxRetries: 5,
446+
retryCondition: () => false,
447+
}
448+
})
441449
})

0 commit comments

Comments
 (0)