If session cache Id changes, reset the cache and pending requests
This commit is contained in:
parent
bc587544ec
commit
df8bf58f03
6 changed files with 84 additions and 3 deletions
5
.changeset/quick-seas-help.md
Normal file
5
.changeset/quick-seas-help.md
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
'@lion/ajax': patch
|
||||
---
|
||||
|
||||
Reset cache and pending requests when cache session ID changes
|
||||
|
|
@ -55,6 +55,7 @@ export default class PendingRequestStore {
|
|||
}
|
||||
|
||||
reset() {
|
||||
this.resolveMatching(/.*/);
|
||||
this._pendingRequests = {};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -32,6 +32,15 @@ export const pendingRequestStore = new PendingRequestStore();
|
|||
*/
|
||||
export const isCurrentSessionId = cacheId => cacheId === cacheSessionId;
|
||||
|
||||
/**
|
||||
* Sets the current cache session ID.
|
||||
*
|
||||
* @param {string} id The id that will be tied to the current session
|
||||
*/
|
||||
export const setCacheSessionId = id => {
|
||||
cacheSessionId = id;
|
||||
};
|
||||
|
||||
/**
|
||||
* Resets the cache session when the cacheId changes.
|
||||
*
|
||||
|
|
@ -43,7 +52,7 @@ export const resetCacheSession = cacheId => {
|
|||
throw new Error('Invalid cache identifier');
|
||||
}
|
||||
if (!isCurrentSessionId(cacheId)) {
|
||||
cacheSessionId = cacheId;
|
||||
setCacheSessionId(cacheId);
|
||||
ajaxCache.reset();
|
||||
pendingRequestStore.reset();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -96,6 +96,12 @@ const createCacheRequestInterceptor =
|
|||
if (pendingRequest) {
|
||||
// there is another concurrent request, wait for it to finish
|
||||
await pendingRequest;
|
||||
|
||||
// If session ID changes while waiting for the pending request to complete,
|
||||
// then do not read the cache.
|
||||
if (!isCurrentSessionId(request.cacheSessionId)) {
|
||||
return request;
|
||||
}
|
||||
}
|
||||
|
||||
const cachedResponse = ajaxCache.get(requestId, { maxAge, maxResponseSize });
|
||||
|
|
|
|||
|
|
@ -503,7 +503,7 @@ describe('Ajax', () => {
|
|||
expect(customAjax._responseInterceptors.length).to.equal(1);
|
||||
});
|
||||
|
||||
describe('caching interceptors', async () => {
|
||||
describe('Caching interceptors: synchronous getCacheIdentifier tests', async () => {
|
||||
/**
|
||||
* @type {Ajax}
|
||||
*/
|
||||
|
|
@ -530,6 +530,30 @@ describe('Ajax', () => {
|
|||
expect(secondResponse.headers.get('Content-Type')).to.equal('application/json');
|
||||
});
|
||||
|
||||
it('resets the cache if the cache ID changes', async () => {
|
||||
/* Three calls to the same endpoint should result in a
|
||||
single fetchStubCall due to caching */
|
||||
await customAjax.fetch('/foo');
|
||||
await customAjax.fetch('/foo');
|
||||
await customAjax.fetch('/foo');
|
||||
expect(fetchStub.callCount).to.equal(1);
|
||||
|
||||
newCacheId();
|
||||
await customAjax.fetch('/foo');
|
||||
|
||||
/* The newCacheId call should reset the cache, thereby adding an
|
||||
extra call to the fetchStub call count. */
|
||||
expect(fetchStub.callCount).to.equal(2);
|
||||
});
|
||||
|
||||
it('Completes pending requests when the cache ID changes', async () => {
|
||||
const requestOne = customAjax.fetch('/foo').then(() => 'completedRequestOne');
|
||||
newCacheId();
|
||||
const requestTwo = customAjax.fetch('/foo').then(() => 'completedRequestTwo');
|
||||
expect(await requestOne).to.equal('completedRequestOne');
|
||||
expect(await requestTwo).to.equal('completedRequestTwo');
|
||||
});
|
||||
|
||||
it('works with fetchJson', async () => {
|
||||
fetchStub.returns(Promise.resolve(new Response('{"a":1,"b":2}', responseInit())));
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,12 @@ import { Ajax, createCacheInterceptors } from '@lion/ajax';
|
|||
import { isResponseContentTypeSupported } from '../../src/interceptors/cacheInterceptors.js';
|
||||
|
||||
// TODO: these are private API? should they be exposed? if not why do we test them?
|
||||
import { extendCacheOptions, resetCacheSession, ajaxCache } from '../../src/cacheManager.js';
|
||||
import {
|
||||
extendCacheOptions,
|
||||
resetCacheSession,
|
||||
ajaxCache,
|
||||
setCacheSessionId,
|
||||
} from '../../src/cacheManager.js';
|
||||
|
||||
const MOCK_RESPONSE = 'mock response';
|
||||
|
||||
|
|
@ -437,6 +442,37 @@ describe('cache interceptors', () => {
|
|||
expect(fetchStub.callCount).to.equal(1);
|
||||
});
|
||||
|
||||
it('Does not use cached request when session ID changes during processing a pending request', async () => {
|
||||
addCacheInterceptors(ajax, {
|
||||
useCache: true,
|
||||
maxAge: 750,
|
||||
});
|
||||
|
||||
// Reset cache
|
||||
newCacheId();
|
||||
|
||||
/* Bump sessionID manually in an injected response interceptor.
|
||||
This will simulate the cache session ID getting changed while
|
||||
waiting for a pending request */
|
||||
|
||||
ajax._responseInterceptors.unshift(async (/** @type {Response} */ response) => {
|
||||
newCacheId();
|
||||
setCacheSessionId(getCacheIdentifier());
|
||||
return response;
|
||||
});
|
||||
|
||||
const requestOne = ajax.fetch('/foo').then(() => 'completedRequestOne');
|
||||
const requestTwo = ajax.fetch('/foo').then(() => 'completedRequestTwo');
|
||||
expect(await requestOne).to.equal('completedRequestOne');
|
||||
// At this point the response interceptor of requestOne has called setCacheSessionId
|
||||
expect(await requestTwo).to.equal('completedRequestTwo');
|
||||
|
||||
/* Neither call should use the cache. During the first call there is no cache entry for '/foo'.
|
||||
During the second call there is, but since the first call's injected interceptor has bumped
|
||||
the cache session ID, it shouldn't use the cached response. */
|
||||
expect(fetchStub.callCount).to.equal(2);
|
||||
});
|
||||
|
||||
it('does save to the cache when `maxResponseSize` is specified and the response size is within the threshold', async () => {
|
||||
// Given
|
||||
newCacheId();
|
||||
|
|
|
|||
Loading…
Reference in a new issue