From b6c5d1fbb9fd3d570c5e9915793c6d80785dd9cb Mon Sep 17 00:00:00 2001 From: Andrej Pavlovic Date: Wed, 18 Sep 2024 22:40:30 -0400 Subject: [PATCH] Timeout triggered on `createApi` endpoint now correctly returns `TIMEOUT_ERROR` instead of generic `AbortError`. --- packages/toolkit/src/query/fetchBaseQuery.ts | 15 ++++++-- .../toolkit/src/query/tests/createApi.test.ts | 35 +++++++++++++++++++ 2 files changed, 47 insertions(+), 3 deletions(-) diff --git a/packages/toolkit/src/query/fetchBaseQuery.ts b/packages/toolkit/src/query/fetchBaseQuery.ts index 00cf6e54ab..8e9ecba972 100644 --- a/packages/toolkit/src/query/fetchBaseQuery.ts +++ b/packages/toolkit/src/query/fetchBaseQuery.ts @@ -213,7 +213,7 @@ export function fetchBaseQuery({ ) } return async (arg, api) => { - const { signal, getState, extra, endpoint, forced, type } = api + const { getState, extra, endpoint, forced, type } = api let meta: FetchBaseQueryMeta | undefined let { url, @@ -224,6 +224,14 @@ export function fetchBaseQuery({ timeout = defaultTimeout, ...rest } = typeof arg == 'string' ? { url: arg } : arg + + let abortController: AbortController | undefined, signal = api.signal + if (timeout) { + abortController = new AbortController() + api.signal.addEventListener('abort', abortController.abort) + signal = abortController.signal + } + let config: RequestInit = { ...baseFetchOptions, signal, @@ -272,10 +280,10 @@ export function fetchBaseQuery({ let response, timedOut = false, timeoutId = - timeout && + abortController && setTimeout(() => { timedOut = true - api.abort() + abortController.abort() }, timeout) try { response = await fetchFn(request) @@ -289,6 +297,7 @@ export function fetchBaseQuery({ } } finally { if (timeoutId) clearTimeout(timeoutId) + abortController?.signal.removeEventListener('abort', abortController.abort) } const responseClone = response.clone() diff --git a/packages/toolkit/src/query/tests/createApi.test.ts b/packages/toolkit/src/query/tests/createApi.test.ts index ae745b18b2..9c97dc6465 100644 --- a/packages/toolkit/src/query/tests/createApi.test.ts +++ b/packages/toolkit/src/query/tests/createApi.test.ts @@ -1128,3 +1128,38 @@ describe('custom serializeQueryArgs per endpoint', () => { }) }) }) + +describe('timeout behavior', () => { + test('triggers TIMEOUT_ERROR', async () => { + const api = createApi({ + baseQuery: fetchBaseQuery({ baseUrl: 'https://example.com', timeout: 5 }), + endpoints: (build) => ({ + query: build.query({ + query: () => '/success', + }), + }), + }) + + const storeRef = setupApiStore(api, undefined, { + withoutTestLifecycles: true, + }) + + server.use( + http.get( + 'https://example.com/success', + async () => { + await delay(10) + return HttpResponse.json({ value: 'failed' }, { status: 500 }) + }, + { once: true }, + ), + ) + + const result = await storeRef.store.dispatch(api.endpoints.query.initiate()) + + expect(result?.error).toEqual({ + status: 'TIMEOUT_ERROR', + error: expect.stringMatching(/^AbortError:/), + }) + }) +})