From 0a82005bf94a3e33fe8c1ff3ddcce60cf10cc448 Mon Sep 17 00:00:00 2001 From: Wykks Date: Fri, 18 Oct 2024 18:28:11 +0200 Subject: [PATCH] fix: aborted requests do not clear its cache afterwards if previous request was cached (#922) --- src/interceptors/response.ts | 16 +++++-- test/interceptors/response.test.ts | 74 ++++++++++++++++++++---------- 2 files changed, 63 insertions(+), 27 deletions(-) diff --git a/src/interceptors/response.ts b/src/interceptors/response.ts index 8361f84b..a62b6bbb 100644 --- a/src/interceptors/response.ts +++ b/src/interceptors/response.ts @@ -15,9 +15,15 @@ export function defaultResponseInterceptor(axios: AxiosCacheInstance): ResponseI * * Also update the waiting list for this key by rejecting it. */ - const rejectResponse = async (responseId: string, config: CacheRequestConfig) => { + const rejectResponse = async ( + responseId: string, + config: CacheRequestConfig, + clearCache = true + ) => { // Updates the cache to empty to prevent infinite loading state - await axios.storage.remove(responseId, config); + if (clearCache) { + await axios.storage.remove(responseId, config); + } // Rejects the deferred, if present const deferred = axios.waiting.get(responseId); @@ -297,7 +303,11 @@ export function defaultResponseInterceptor(axios: AxiosCacheInstance): ResponseI } // Rejects all other requests waiting for this response - await rejectResponse(id, config); + await rejectResponse( + id, + config, + error.code !== 'ERR_CANCELED' || (error.code === 'ERR_CANCELED' && cache.state !== 'cached') + ); throw error; } diff --git a/test/interceptors/response.test.ts b/test/interceptors/response.test.ts index cd5bd15c..1b98ee92 100644 --- a/test/interceptors/response.test.ts +++ b/test/interceptors/response.test.ts @@ -10,7 +10,7 @@ describe('Response Interceptor', () => { it('`storage.get` call without specified methods', async () => { const axios = mockAxios({ // only cache post methods - methods: ['post'] + methods: ['post'], }); const spy = mock.method(axios.storage, 'get'); @@ -22,7 +22,7 @@ describe('Response Interceptor', () => { it('`storage.get` call with specified methods', async () => { const axios = mockAxios({ // only cache get methods - methods: ['get'] + methods: ['get'], }); const spy = mock.method(axios.storage, 'get'); @@ -71,9 +71,9 @@ describe('Response Interceptor', () => { axios.get('http://test.com', { cache: { cachePredicate: { - responseMatch: () => false - } - } + responseMatch: () => false, + }, + }, }); // Make first request to cache it @@ -112,9 +112,9 @@ describe('Response Interceptor', () => { await axios.get('key02', { cache: { update: { - [id]: 'delete' as const - } - } + [id]: 'delete' as const, + }, + }, }); const cache = await axios.storage.get(id); @@ -125,15 +125,12 @@ describe('Response Interceptor', () => { it('Blank CacheControl header', async () => { const defaultTtl = 60; - const axios = mockAxios( - { ttl: defaultTtl, interpretHeader: true }, - { [Header.CacheControl]: '' } - ); + const axios = mockAxios({ ttl: defaultTtl, interpretHeader: true }, { [Header.CacheControl]: '' }); const { id } = await axios.get('key01', { cache: { - interpretHeader: true - } + interpretHeader: true, + }, }); const cache = await axios.storage.get(id); @@ -161,8 +158,8 @@ describe('Response Interceptor', () => { assert.ok(resp.data); return 100; - } - } + }, + }, }); const cache1 = await axios.storage.get(id); @@ -174,7 +171,7 @@ describe('Response Interceptor', () => { await axios.get('url', { id, - cache: { ttl } + cache: { ttl }, }); const cache2 = await axios.storage.get(id); @@ -203,8 +200,8 @@ describe('Response Interceptor', () => { }); }, 20); }); - } - } + }, + }, }); const cache = await axios.storage.get(id); @@ -229,7 +226,7 @@ describe('Response Interceptor', () => { { [Header.XAxiosCacheEtag]: headerValue, [Header.XAxiosCacheLastModified]: headerValue, - [Header.XAxiosCacheStaleIfError]: headerValue + [Header.XAxiosCacheStaleIfError]: headerValue, } ); @@ -279,13 +276,13 @@ describe('Response Interceptor', () => { // fresh response from server and transformed const freshResponse = await axios.get('url', { - transformResponse: (data: unknown) => [data] + transformResponse: (data: unknown) => [data], }); // cached response // should not transform again as already in desired format const cachedResponse = await axios.get('url', { - transformResponse: (data: unknown) => [data] + transformResponse: (data: unknown) => [data], }); assert.deepEqual(freshResponse.data, [true]); @@ -299,7 +296,7 @@ describe('Response Interceptor', () => { const promise = axios.get('url', { transformResponse: () => { throw error; - } + }, }); await assert.rejects(promise, error); @@ -347,7 +344,7 @@ describe('Response Interceptor', () => { // Simulates previous unresolved request await axios.storage.set(id, { state: 'loading', - previous: 'empty' + previous: 'empty', }); const response = await axios.get('url', { id }); @@ -361,4 +358,33 @@ describe('Response Interceptor', () => { assert.equal(storage.state, 'cached'); assert.equal(storage.data?.data, true); }); + + // https://github.com/arthurfiorette/axios-cache-interceptor/issues/922 + it('Aborted requests do not clear its cache afterwards if previous request was cached', async () => { + const axios = mockAxios(); + + const id = '1'; + + // first request + await axios.get('url', { id }); + + // Second request cancelled + const controller = new AbortController(); + const cancelled = axios.get('url', { id, signal: controller.signal }); + controller.abort(); + try { + await cancelled; + assert.fail('should have thrown an error'); + } catch (error: any) { + assert.equal(error.code, 'ERR_CANCELED'); + } + + // Third request + const promise = axios.get('url', { id }); + + const response = await promise; + + // Third request should still have cached data + await assert.equal(response.cached, true); + }); });