Update fetchCacheUrl
- Rename to fetchCacheJSON: the method expects the response to be a JSON
- Add documentation and clearly state which requests are considered
"same"
- Include url params in the cache key: None of the existing call sites
set params, but it's error-prone and surprising not to include them.
Params commonly influence the value of the returned response. Since
none of the callers set params at the moment, this is guaranteed safe.
- Add corresponding test for the params
Google-Bug-Id: b/297849592
Release-Notes: skip
Change-Id: Id008a6cfa1462775f18c79ea490e8d7ea918a97e
diff --git a/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-apis/gr-rest-api-helper.ts b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-apis/gr-rest-api-helper.ts
index 556d976..d5bba65 100644
--- a/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-apis/gr-rest-api-helper.ts
+++ b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-apis/gr-rest-api-helper.ts
@@ -440,30 +440,40 @@
return req;
}
- fetchCacheURL(req: FetchRequest): Promise<ParsedJSON | undefined> {
- if (this._fetchPromisesCache.has(req.url)) {
- return this._fetchPromisesCache.get(req.url)!;
+ /**
+ * Fetch JSON using cached value if available.
+ *
+ * If there is an in-flight request with the same url returns the promise for
+ * the in-flight request. If previous call for the same url resulted in the
+ * successful response it is returned. Otherwise a new request is sent.
+ *
+ * Only req.url with req.params is considered for the caching key;
+ * headers or request body are not included in cache key.
+ */
+ fetchCacheJSON(req: FetchRequest): Promise<ParsedJSON | undefined> {
+ const urlWithParams = this.urlWithParams(req.url, req.params);
+ if (this._fetchPromisesCache.has(urlWithParams)) {
+ return this._fetchPromisesCache.get(urlWithParams)!;
}
- // TODO(andybons): Periodic cache invalidation.
- if (this._cache.has(req.url)) {
- return Promise.resolve(this._cache.get(req.url)!);
+ if (this._cache.has(urlWithParams)) {
+ return Promise.resolve(this._cache.get(urlWithParams)!);
}
this._fetchPromisesCache.set(
- req.url,
+ urlWithParams,
this.fetchJSON(req)
.then(response => {
if (response !== undefined) {
- this._cache.set(req.url, response);
+ this._cache.set(urlWithParams, response);
}
- this._fetchPromisesCache.set(req.url, undefined);
+ this._fetchPromisesCache.set(urlWithParams, undefined);
return response;
})
.catch(err => {
- this._fetchPromisesCache.set(req.url, undefined);
+ this._fetchPromisesCache.set(urlWithParams, undefined);
throw err;
})
);
- return this._fetchPromisesCache.get(req.url)!;
+ return this._fetchPromisesCache.get(urlWithParams)!;
}
// if errFn is not set, then only Response possible
diff --git a/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-apis/gr-rest-api-helper_test.ts b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-apis/gr-rest-api-helper_test.ts
index a212e9d..be1686d 100644
--- a/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-apis/gr-rest-api-helper_test.ts
+++ b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-apis/gr-rest-api-helper_test.ts
@@ -263,6 +263,69 @@
});
});
+ test('cached results', () => {
+ let n = 0;
+ sinon
+ .stub(helper, 'fetchJSON')
+ .callsFake(() => Promise.resolve(makeParsedJSON(++n)));
+ const promises = [];
+ promises.push(helper.fetchCacheJSON({url: '/foo'}));
+ promises.push(helper.fetchCacheJSON({url: '/foo'}));
+ promises.push(helper.fetchCacheJSON({url: '/foo'}));
+
+ return Promise.all(promises).then(results => {
+ assert.deepEqual(results, [
+ makeParsedJSON(1),
+ makeParsedJSON(1),
+ makeParsedJSON(1),
+ ]);
+ return helper.fetchCacheJSON({url: '/foo'}).then(foo => {
+ assert.equal(foo, makeParsedJSON(1));
+ });
+ });
+ });
+
+ test('cached results with param', () => {
+ let n = 0;
+ sinon
+ .stub(helper, 'fetchJSON')
+ .callsFake(() => Promise.resolve(makeParsedJSON(++n)));
+ const promises = [];
+ promises.push(
+ helper.fetchCacheJSON({url: '/foo', params: {hello: 'world'}})
+ );
+ promises.push(helper.fetchCacheJSON({url: '/foo'}));
+ promises.push(
+ helper.fetchCacheJSON({url: '/foo', params: {hello: 'world'}})
+ );
+
+ return Promise.all(promises).then(results => {
+ assert.deepEqual(results, [
+ makeParsedJSON(1),
+ // The url without params is queried again, since it has different url.
+ makeParsedJSON(2),
+ makeParsedJSON(1),
+ ]);
+ return helper
+ .fetchCacheJSON({url: '/foo', params: {hello: 'world'}})
+ .then(foo => {
+ assert.equal(foo, makeParsedJSON(1));
+ });
+ });
+ });
+
+ test('cache invalidation', async () => {
+ cache.set('/foo/bar', makeParsedJSON(1));
+ cache.set('/bar', makeParsedJSON(2));
+ fetchPromisesCache.set('/foo/bar', Promise.resolve(makeParsedJSON(3)));
+ fetchPromisesCache.set('/bar', Promise.resolve(makeParsedJSON(4)));
+ helper.invalidateFetchPromisesPrefix('/foo/');
+ assert.isFalse(cache.has('/foo/bar'));
+ assert.isTrue(cache.has('/bar'));
+ assert.isUndefined(fetchPromisesCache.get('/foo/bar'));
+ assert.strictEqual(makeParsedJSON(4), await fetchPromisesCache.get('/bar'));
+ });
+
test('params are properly encoded', () => {
let url = helper.urlWithParams('/path/', {
sp: 'hola',