fix: infinite loading states when a restart occurs in the middle of a request. (#612)

This commit is contained in:
arthurfiorette 2023-09-03 18:53:26 -03:00
parent b352f12a91
commit 130ef0d306
No known key found for this signature in database
GPG Key ID: 9D190CD53C53C555
4 changed files with 66 additions and 9 deletions

View File

@ -48,6 +48,7 @@
"format": "prettier --write .",
"lint": "eslint . --ext .ts",
"test": "NODE_V8_COVERAGE=coverage node -r ./dist/test/setup.js --enable-source-maps --trace-warnings --experimental-test-coverage --test dist/test/**/*.test.js",
"test:only": "tsc -p tsconfig.test.json && NODE_V8_COVERAGE=coverage node -r ./dist/test/setup.js --enable-source-maps --trace-warnings --experimental-test-coverage --test-only",
"version": "auto-changelog -p && cp CHANGELOG.md docs/src/others/changelog.md && git add CHANGELOG.md docs/src/others/changelog.md"
},
"resolutions": {

View File

@ -2,7 +2,7 @@ import type { CacheAxiosResponse, InternalCacheRequestConfig } from '../cache/ax
/** See {@link AxiosInterceptorManager} */
export interface AxiosInterceptor<T> {
onFulfilled?(value: T): T | Promise<T>;
onFulfilled(value: T): T | Promise<T>;
/** Returns a successful response or re-throws the error */
onRejected?(error: Record<string, unknown>): T | Promise<T>;

View File

@ -164,11 +164,10 @@ export function defaultRequestInterceptor(axios: AxiosCacheInstance) {
if (cache.state === 'loading') {
const deferred = axios.waiting[config.id];
// Just in case, the deferred doesn't exists.
/* istanbul ignore if 'really hard to test' */
// The deferred may not exists when the process is using a persistent
// storage and cancelled in the middle of a request, this would result in
// a pending loading state in the storage but no current promises to resolve
if (!deferred) {
await axios.storage.remove(config.id, config);
// Hydrates any UI temporarily, if cache is available
if (cache.data) {
await config.cache.hydrate?.(cache);
@ -201,8 +200,9 @@ export function defaultRequestInterceptor(axios: AxiosCacheInstance) {
await config.cache.hydrate?.(cache);
}
// The deferred is rejected when the request that we are waiting rejected cache.
return config;
// The deferred is rejected when the request that we are waiting rejects its cache.
// In this case, we need to redo the request all over again.
return onFulfilled(config);
}
} else {
cachedResponse = cache.data;
@ -210,8 +210,8 @@ export function defaultRequestInterceptor(axios: AxiosCacheInstance) {
// Even though the response interceptor receives this one from here,
// it has been configured to ignore cached responses = true
config.adapter = (): Promise<CacheAxiosResponse> =>
Promise.resolve({
config.adapter = function cachedAdapter(): Promise<CacheAxiosResponse> {
return Promise.resolve({
config,
data: cachedResponse.data,
headers: cachedResponse.headers,
@ -221,6 +221,7 @@ export function defaultRequestInterceptor(axios: AxiosCacheInstance) {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
id: config.id!
});
};
if (__ACI_DEV__) {
axios.debug?.({

View File

@ -296,4 +296,59 @@ describe('Response Interceptor', () => {
await assert.rejects(promise, error);
});
it('Cancelled deferred still should save cache after new response', async () => {
const axios = mockAxios();
const id = '1';
const controller = new AbortController();
const cancelled = axios.get('url', { id, signal: controller.signal });
const promise = axios.get('url', { id });
controller.abort();
// p1 should fail as it was aborted
try {
await cancelled;
assert.fail('should have thrown an error');
} catch (error: any) {
assert.equal(error.code, 'ERR_CANCELED');
}
const response = await promise;
// p2 should succeed as it was not aborted
await assert.ok(response.data);
await assert.equal(response.cached, false);
const storage = await axios.storage.get(id);
// P2 should have saved the cache
// even that his origin was from a cancelled deferred
assert.equal(storage.state, 'cached');
assert.equal(storage.data?.data, true);
});
it('Response gets cached even if there is a pending request without deferred.', async () => {
const axios = mockAxios();
const id = '1';
// Simulates previous unresolved request
await axios.storage.set(id, {
state: 'loading',
previous: 'empty'
});
const response = await axios.get('url', { id });
assert.equal(response.cached, false);
assert.ok(response.data);
const storage = await axios.storage.get(id);
assert.equal(storage.state, 'cached');
assert.equal(storage.data?.data, true);
});
});