Skip to content

Commit 4f9a7b8

Browse files
authored
improve error handling of fetchBaseQuery (#1250)
1 parent 30ecd11 commit 4f9a7b8

File tree

5 files changed

+316
-56
lines changed

5 files changed

+316
-56
lines changed

packages/toolkit/src/query/fetchBaseQuery.ts

Lines changed: 58 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -58,10 +58,38 @@ const handleResponse = async (
5858
}
5959
}
6060

61-
export interface FetchBaseQueryError {
62-
status: number
63-
data: unknown
64-
}
61+
export type FetchBaseQueryError =
62+
| {
63+
/**
64+
* * `number`:
65+
* HTTP status code
66+
*/
67+
status: number
68+
data: unknown
69+
statusText: string
70+
}
71+
| {
72+
/**
73+
* * `"FETCH_ERROR"`:
74+
* An error that occured during execution of `fetch` or the `fetchFn` callback option
75+
**/
76+
status: 'FETCH_ERROR'
77+
data?: undefined
78+
error: string
79+
}
80+
| {
81+
/**
82+
* * `"PARSING_ERROR"`:
83+
* An error happened during parsing.
84+
* Most likely a non-JSON-response was returned with the default `responseHandler` "JSON",
85+
* or an error occured while executing a custom `responseHandler`.
86+
**/
87+
status: 'PARSING_ERROR'
88+
originalStatus: number
89+
statusText: string
90+
data: string
91+
error: string
92+
}
6593

6694
function stripUndefined(obj: any) {
6795
if (!isPlainObject(obj)) {
@@ -86,7 +114,7 @@ export type FetchBaseQueryArgs = {
86114
) => Promise<Response>
87115
} & RequestInit
88116

89-
export type FetchBaseQueryMeta = { request: Request; response: Response }
117+
export type FetchBaseQueryMeta = { request: Request; response?: Response }
90118

91119
/**
92120
* This is a very small wrapper around fetch that aims to simplify requests.
@@ -140,6 +168,7 @@ export function fetchBaseQuery({
140168
)
141169
}
142170
return async (arg, { signal, getState }) => {
171+
let meta: FetchBaseQueryMeta | undefined
143172
let {
144173
url,
145174
method = 'GET' as const,
@@ -188,13 +217,33 @@ export function fetchBaseQuery({
188217

189218
const request = new Request(url, config)
190219
const requestClone = request.clone()
220+
meta = { request: requestClone }
191221

192-
const response = await fetchFn(request)
222+
let response
223+
try {
224+
response = await fetchFn(request)
225+
} catch (e) {
226+
return { error: { status: 'FETCH_ERROR', error: String(e) }, meta }
227+
}
193228
const responseClone = response.clone()
194229

195-
const meta = { request: requestClone, response: responseClone }
230+
meta.response = responseClone
196231

197-
const resultData = await handleResponse(response, responseHandler)
232+
let resultData
233+
try {
234+
resultData = await handleResponse(response, responseHandler)
235+
} catch (e) {
236+
return {
237+
error: {
238+
status: 'PARSING_ERROR',
239+
originalStatus: response.status,
240+
statusText: response.statusText,
241+
data: await responseClone.clone().text(),
242+
error: String(e),
243+
},
244+
meta,
245+
}
246+
}
198247

199248
return validateStatus(response, resultData)
200249
? {
@@ -205,6 +254,7 @@ export function fetchBaseQuery({
205254
error: {
206255
status: response.status,
207256
data: resultData,
257+
statusText: response.statusText,
208258
},
209259
meta,
210260
}

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -482,7 +482,7 @@ describe('additional transformResponse behaviors', () => {
482482
meta: {
483483
request: { headers: getSerializedHeaders(meta?.request.headers) },
484484
response: {
485-
headers: getSerializedHeaders(meta?.response.headers),
485+
headers: getSerializedHeaders(meta?.response?.headers),
486486
},
487487
},
488488
}
@@ -507,7 +507,7 @@ describe('additional transformResponse behaviors', () => {
507507
meta: {
508508
request: { headers: getSerializedHeaders(meta?.request.headers) },
509509
response: {
510-
headers: getSerializedHeaders(meta?.response.headers),
510+
headers: getSerializedHeaders(meta?.response?.headers),
511511
},
512512
},
513513
}

packages/toolkit/src/query/tests/errorHandling.test.tsx

Lines changed: 77 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,9 @@
11
import * as React from 'react'
2-
import type {
3-
BaseQueryFn} from '@reduxjs/toolkit/query/react';
4-
import {
5-
createApi,
6-
fetchBaseQuery,
7-
} from '@reduxjs/toolkit/query/react'
2+
import type { BaseQueryFn } from '@reduxjs/toolkit/query/react'
3+
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'
84
import { renderHook, act } from '@testing-library/react-hooks'
95
import { rest } from 'msw'
10-
import type { AxiosError, AxiosRequestConfig, AxiosResponse } from 'axios';
6+
import type { AxiosError, AxiosRequestConfig, AxiosResponse } from 'axios'
117
import axios from 'axios'
128

139
import { expectExactType, hookWaitFor, setupApiStore } from './helpers'
@@ -69,7 +65,11 @@ describe('fetchBaseQuery', () => {
6965
{}
7066
)
7167
).resolves.toEqual({
72-
error: { data: { value: 'error' }, status: 500 },
68+
error: {
69+
data: { value: 'error' },
70+
statusText: 'Internal Server Error',
71+
status: 500,
72+
},
7373
meta: {
7474
request: expect.any(Object),
7575
response: expect.any(Object),
@@ -116,7 +116,11 @@ describe('query error handling', () => {
116116
isLoading: false,
117117
isError: true,
118118
isSuccess: false,
119-
error: { status: 500, data: { value: 'error' } },
119+
error: {
120+
status: 500,
121+
statusText: 'Internal Server Error',
122+
data: { value: 'error' },
123+
},
120124
})
121125
)
122126
})
@@ -155,7 +159,11 @@ describe('query error handling', () => {
155159
isLoading: false,
156160
isError: true,
157161
isSuccess: false,
158-
error: { status: 500, data: { value: 'error' } },
162+
error: {
163+
status: 500,
164+
statusText: 'Internal Server Error',
165+
data: { value: 'error' },
166+
},
159167
// last data will stay available
160168
data: { value: 'success' },
161169
})
@@ -183,7 +191,11 @@ describe('query error handling', () => {
183191
isLoading: false,
184192
isError: true,
185193
isSuccess: false,
186-
error: { status: 500, data: { value: 'error' } },
194+
error: {
195+
status: 500,
196+
statusText: 'Internal Server Error',
197+
data: { value: 'error' },
198+
},
187199
})
188200
)
189201

@@ -247,7 +259,11 @@ describe('mutation error handling', () => {
247259
isLoading: false,
248260
isError: true,
249261
isSuccess: false,
250-
error: { status: 500, data: { value: 'error' } },
262+
error: {
263+
status: 500,
264+
statusText: 'Internal Server Error',
265+
data: { value: 'error' },
266+
},
251267
})
252268
)
253269
})
@@ -295,7 +311,11 @@ describe('mutation error handling', () => {
295311
isLoading: false,
296312
isError: true,
297313
isSuccess: false,
298-
error: { status: 500, data: { value: 'error' } },
314+
error: {
315+
status: 500,
316+
statusText: 'Internal Server Error',
317+
data: { value: 'error' },
318+
},
299319
})
300320
)
301321
expect(result.current[1].data).toBeUndefined()
@@ -329,7 +349,11 @@ describe('mutation error handling', () => {
329349
isLoading: false,
330350
isError: true,
331351
isSuccess: false,
332-
error: { status: 500, data: { value: 'error' } },
352+
error: {
353+
status: 500,
354+
statusText: 'Internal Server Error',
355+
data: { value: 'error' },
356+
},
333357
})
334358
)
335359
}
@@ -353,34 +377,39 @@ describe('mutation error handling', () => {
353377
})
354378

355379
describe('custom axios baseQuery', () => {
356-
const axiosBaseQuery = (
357-
{ baseUrl }: { baseUrl: string } = { baseUrl: '' }
358-
): BaseQueryFn<
359-
{
360-
url: string
361-
method: AxiosRequestConfig['method']
362-
data?: AxiosRequestConfig['data']
363-
},
364-
unknown,
365-
unknown,
366-
unknown,
367-
{ response: AxiosResponse; request: AxiosRequestConfig }
368-
> => async ({ url, method, data }) => {
369-
const config = { url: baseUrl + url, method, data }
370-
try {
371-
const result = await axios(config)
372-
return { data: result.data, meta: { request: config, response: result } }
373-
} catch (axiosError) {
374-
let err = axiosError as AxiosError
375-
return {
376-
error: {
377-
status: err.response?.status,
378-
data: err.response?.data,
379-
},
380-
meta: { request: config, response: err.response as AxiosResponse },
380+
const axiosBaseQuery =
381+
(
382+
{ baseUrl }: { baseUrl: string } = { baseUrl: '' }
383+
): BaseQueryFn<
384+
{
385+
url: string
386+
method: AxiosRequestConfig['method']
387+
data?: AxiosRequestConfig['data']
388+
},
389+
unknown,
390+
unknown,
391+
unknown,
392+
{ response: AxiosResponse; request: AxiosRequestConfig }
393+
> =>
394+
async ({ url, method, data }) => {
395+
const config = { url: baseUrl + url, method, data }
396+
try {
397+
const result = await axios(config)
398+
return {
399+
data: result.data,
400+
meta: { request: config, response: result },
401+
}
402+
} catch (axiosError) {
403+
let err = axiosError as AxiosError
404+
return {
405+
error: {
406+
status: err.response?.status,
407+
data: err.response?.data,
408+
},
409+
meta: { request: config, response: err.response as AxiosResponse },
410+
}
381411
}
382412
}
383-
}
384413

385414
type SuccessResponse = { value: 'success' }
386415
const api = createApi({
@@ -461,10 +490,8 @@ describe('error handling in a component', () => {
461490

462491
function User() {
463492
const [manualError, setManualError] = React.useState<any>()
464-
const [
465-
update,
466-
{ isLoading, data, error },
467-
] = api.endpoints.update.useMutation()
493+
const [update, { isLoading, data, error }] =
494+
api.endpoints.update.useMutation()
468495

469496
return (
470497
<div>
@@ -558,7 +585,11 @@ describe('error handling in a component', () => {
558585
})
559586
const result = await mutationqueryFulfilled!
560587
expect(result).toMatchObject({
561-
error: { status: 500, data: { value: 'error' } },
588+
error: {
589+
status: 500,
590+
statusText: 'Internal Server Error',
591+
data: { value: 'error' },
592+
},
562593
})
563594
})
564595
test(`an un-subscribed mutation will still be unwrappable (success case), track: ${track}`, async () => {
@@ -594,6 +625,7 @@ describe('error handling in a component', () => {
594625
const unwrappedPromise = mutationqueryFulfilled!.unwrap()
595626
expect(unwrappedPromise).rejects.toMatchObject({
596627
status: 500,
628+
statusText: 'Internal Server Error',
597629
data: { value: 'error' },
598630
})
599631
})

0 commit comments

Comments
 (0)