Skip to content

Commit

Permalink
Merge pull request #2823 from reduxjs/draft/inspiring-currying
Browse files Browse the repository at this point in the history
  • Loading branch information
markerikson authored Oct 30, 2022
2 parents 17d1789 + a2fec02 commit 270a14f
Show file tree
Hide file tree
Showing 2 changed files with 106 additions and 23 deletions.
13 changes: 7 additions & 6 deletions packages/toolkit/src/query/fetchBaseQuery.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ export interface FetchArgs extends CustomRequestInit {
body?: any
responseHandler?: ResponseHandler
validateStatus?: (response: Response, body: any) => boolean
/**
* A number in milliseconds that represents that maximum time a request can take before timing out.
*/
timeout?: number
}

Expand Down Expand Up @@ -129,11 +132,8 @@ export type FetchBaseQueryArgs = {
* Defaults to `application/json`;
*/
jsonContentType?: string
/**
* A number in milliseconds that represents that maximum time a request can take before timing out.
*/
timeout?: number
} & RequestInit
} & RequestInit &
Pick<FetchArgs, 'responseHandler' | 'validateStatus' | 'timeout'>

export type FetchBaseQueryMeta = { request: Request; response?: Response }

Expand Down Expand Up @@ -189,6 +189,7 @@ export function fetchBaseQuery({
isJsonContentType = defaultIsJsonContentType,
jsonContentType = 'application/json',
timeout: defaultTimeout,
validateStatus: globalValidateStatus,
...baseFetchOptions
}: FetchBaseQueryArgs = {}): BaseQueryFn<
string | FetchArgs,
Expand All @@ -212,7 +213,7 @@ export function fetchBaseQuery({
body = undefined,
params = undefined,
responseHandler = 'json' as const,
validateStatus = defaultValidateStatus,
validateStatus = globalValidateStatus ?? defaultValidateStatus,
timeout = defaultTimeout,
...rest
} = typeof arg == 'string' ? { url: arg } : arg
Expand Down
116 changes: 99 additions & 17 deletions packages/toolkit/src/query/tests/fetchBaseQuery.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -868,6 +868,89 @@ describe('fetchBaseQuery', () => {
expect(request.headers['delete']).toBe(defaultHeaders['delete'])
expect(request.headers['delete2']).toBe(defaultHeaders['delete2'])
})

describe('Accepts global arguments', () => {
test('Global responseHandler', async () => {
server.use(
rest.get('https://example.com/success', (_, res, ctx) =>
res.once(ctx.text(`this is not json!`))
)
)

const globalizedBaseQuery = fetchBaseQuery({
baseUrl,
fetchFn: fetchFn as any,
responseHandler: 'text',
})

const req = globalizedBaseQuery(
{ url: '/success', responseHandler: 'text' },
commonBaseQueryApi,
{}
)
expect(req).toBeInstanceOf(Promise)
const res = await req
expect(res).toBeInstanceOf(Object)
expect(res.meta?.request).toBeInstanceOf(Request)
expect(res.meta?.response).toBeInstanceOf(Object)
expect(res.data).toEqual(`this is not json!`)
})

test('Global validateStatus', async () => {
const globalizedBaseQuery = fetchBaseQuery({
baseUrl,
fetchFn: fetchFn as any,
validateStatus: (response, body) =>
response.status === 200 && body.success === false ? false : true,
})

// This is a scenario where an API may always return a 200, but indicates there is an error when success = false
const res = await globalizedBaseQuery(
{
url: '/nonstandard-error',
},
commonBaseQueryApi,
{}
)

expect(res.error).toEqual({
status: 200,
data: {
success: false,
message: 'This returns a 200 but is really an error',
},
})
})

test('Global timeout', async () => {
let reject: () => void
const donePromise = new Promise((resolve, _reject) => {
reject = _reject
})
server.use(
rest.get('https://example.com/empty1', async (req, res, ctx) => {
await Promise.race([waitMs(3000), donePromise])
return res.once(ctx.json({ ...req, headers: req.headers.all() }))
})
)
const globalizedBaseQuery = fetchBaseQuery({
baseUrl,
fetchFn: fetchFn as any,
timeout: 200,
})

const result = await globalizedBaseQuery(
{ url: '/empty1' },
commonBaseQueryApi,
{}
)
expect(result?.error).toEqual({
status: 'TIMEOUT_ERROR',
error: 'AbortError: The user aborted a request.',
})
reject!()
})
})
})

describe('fetchFn', () => {
Expand Down Expand Up @@ -950,28 +1033,27 @@ describe('still throws on completely unexpected errors', () => {
})

describe('timeout', () => {
it('throws a timeout error when a request takes longer than specified timeout duration', async () => {
jest.useFakeTimers('legacy')
let result: any
test('throws a timeout error when a request takes longer than specified timeout duration', async () => {
let reject: () => void
const donePromise = new Promise((resolve, _reject) => {
reject = _reject
})

server.use(
rest.get('https://example.com/empty', (req, res, ctx) =>
res.once(
ctx.delay(3000),
ctx.json({ ...req, headers: req.headers.all() })
)
)
rest.get('https://example.com/empty2', async (req, res, ctx) => {
await Promise.race([waitMs(3000), donePromise])
return res.once(ctx.json({ ...req, headers: req.headers.all() }))
})
)
const result = await baseQuery(
{ url: '/empty2', timeout: 200 },
commonBaseQueryApi,
{}
)
Promise.resolve(
baseQuery({ url: '/empty', timeout: 2000 }, commonBaseQueryApi, {})
).then((r) => {
result = r
})
await waitMs()
jest.runAllTimers()
await waitMs()
expect(result?.error).toEqual({
status: 'TIMEOUT_ERROR',
error: 'AbortError: The user aborted a request.',
})
reject!()
})
})

0 comments on commit 270a14f

Please sign in to comment.