Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: aborted requests should not clear its cache afterwards if previous request was cached (#922) #923

Merged
merged 1 commit into from
Oct 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 18 additions & 7 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: boolean
) => {
// 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 @@ -116,7 +122,7 @@ export function defaultResponseInterceptor(axios: AxiosCacheInstance): ResponseI
!cache.data &&
!(await testCachePredicate(response, cacheConfig.cachePredicate))
) {
await rejectResponse(response.id, config);
await rejectResponse(response.id, config, true);

if (__ACI_DEV__) {
axios.debug({
Expand Down Expand Up @@ -154,7 +160,7 @@ export function defaultResponseInterceptor(axios: AxiosCacheInstance): ResponseI

// Cache should not be used
if (expirationTime === 'dont cache') {
await rejectResponse(response.id, config);
await rejectResponse(response.id, config, true);

if (__ACI_DEV__) {
axios.debug({
Expand Down Expand Up @@ -276,7 +282,7 @@ export function defaultResponseInterceptor(axios: AxiosCacheInstance): ResponseI
}

// Rejects all other requests waiting for this response
await rejectResponse(id, config);
await rejectResponse(id, config, true);

throw error;
}
Expand All @@ -297,7 +303,12 @@ export function defaultResponseInterceptor(axios: AxiosCacheInstance): ResponseI
}

// Rejects all other requests waiting for this response
await rejectResponse(id, config);
await rejectResponse(
arthurfiorette marked this conversation as resolved.
Show resolved Hide resolved
id,
config,
// Do not clear cache if this request is cached, but the request was cancelled before returning the cached response
error.code !== 'ERR_CANCELED' || (error.code === 'ERR_CANCELED' && cache.state !== 'cached')
arthurfiorette marked this conversation as resolved.
Show resolved Hide resolved
);

throw error;
}
Expand Down Expand Up @@ -381,7 +392,7 @@ export function defaultResponseInterceptor(axios: AxiosCacheInstance): ResponseI
}

// Rejects all other requests waiting for this response
await rejectResponse(id, config);
await rejectResponse(id, config, true);

throw error;
};
Expand Down
43 changes: 43 additions & 0 deletions test/interceptors/response.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -361,4 +361,47 @@ 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 should preserve non-stale valid cache entries', async () => {
const instance = Axios.create({});
const axios = setupCache(instance, {});

const id = '1';

const cache = {
data: true,
headers: {},
status: 200,
statusText: 'Ok'
};

// Cache request
axios.storage.set(id, {
state: 'cached',
ttl: 5000,
createdAt: Date.now(),
data: cache
});

// First request cancelled immediately
const controller = new AbortController();
const cancelled = axios.get('http://unknown.url.lan:1234', { id, signal: controller.signal });
controller.abort();
arthurfiorette marked this conversation as resolved.
Show resolved Hide resolved
try {
await cancelled;
assert.fail('should have thrown an error');
} catch (error: any) {
assert.equal(error.code, 'ERR_CANCELED');
}

// Second request cancelled after a macrotask
const controller2 = new AbortController();
const promise = axios.get('http://unknown.url.lan:1234', { id, signal: controller2.signal });
// Wait for eventual request to be sent
await new Promise((res) => setTimeout(res));
controller2.abort();
const response = await promise;
assert.ok(response.cached);
});
});