Skip to content

Commit

Permalink
fix: aborted requests do not clear its cache afterwards if previous r…
Browse files Browse the repository at this point in the history
…equest was cached (#922)
  • Loading branch information
Wykks committed Oct 18, 2024
1 parent a7a4e31 commit 0a82005
Show file tree
Hide file tree
Showing 2 changed files with 63 additions and 27 deletions.
16 changes: 13 additions & 3 deletions src/interceptors/response.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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;
}
Expand Down
74 changes: 50 additions & 24 deletions test/interceptors/response.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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');
Expand All @@ -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');
Expand Down Expand Up @@ -71,9 +71,9 @@ describe('Response Interceptor', () => {
axios.get('http://test.com', {
cache: {
cachePredicate: {
responseMatch: () => false
}
}
responseMatch: () => false,
},
},
});

// Make first request to cache it
Expand Down Expand Up @@ -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);
Expand All @@ -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);
Expand Down Expand Up @@ -161,8 +158,8 @@ describe('Response Interceptor', () => {
assert.ok(resp.data);

return 100;
}
}
},
},
});

const cache1 = await axios.storage.get(id);
Expand All @@ -174,7 +171,7 @@ describe('Response Interceptor', () => {

await axios.get('url', {
id,
cache: { ttl }
cache: { ttl },
});

const cache2 = await axios.storage.get(id);
Expand Down Expand Up @@ -203,8 +200,8 @@ describe('Response Interceptor', () => {
});
}, 20);
});
}
}
},
},
});

const cache = await axios.storage.get(id);
Expand All @@ -229,7 +226,7 @@ describe('Response Interceptor', () => {
{
[Header.XAxiosCacheEtag]: headerValue,
[Header.XAxiosCacheLastModified]: headerValue,
[Header.XAxiosCacheStaleIfError]: headerValue
[Header.XAxiosCacheStaleIfError]: headerValue,
}
);

Expand Down Expand Up @@ -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]);
Expand All @@ -299,7 +296,7 @@ describe('Response Interceptor', () => {
const promise = axios.get('url', {
transformResponse: () => {
throw error;
}
},
});

await assert.rejects(promise, error);
Expand Down Expand Up @@ -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 });
Expand All @@ -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);
});
});

0 comments on commit 0a82005

Please sign in to comment.