Skip to content

Commit b21a54a

Browse files
authored
fix: delayed background de-duplication and deprecate deprecate-staleRefreshTimeout (#134)
* fix: de-duplicate delayed background refreshes prior to this syncrounous calls or cases with staleRefreshTimeout > 0 would queue up a lot of work and thereby behave different to the default de-duplication behavior this change queues up background refreshes imediately and then delays the potentially bigger work in `getFreshValue` fix #132 * fix: deprecate staleRefreshTimeout ref #132 * refactor: remove unnecessary promise wrapper
1 parent b027dad commit b21a54a

File tree

4 files changed

+62
-26
lines changed

4 files changed

+62
-26
lines changed

README.md

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -224,15 +224,6 @@ interface CachifiedOptions<Value> {
224224
* Default: `Infinity`
225225
*/
226226
fallbackToCache?: boolean | number;
227-
/**
228-
* Amount of time in milliseconds before revalidation of a stale
229-
* cache entry is started
230-
*
231-
* Must be positive and finite
232-
*
233-
* Default: `0`
234-
*/
235-
staleRefreshTimeout?: number;
236227
/**
237228
* Promises passed to `waitUntil` represent background tasks which must be
238229
* completed before the server can shutdown. e.g. swr cache revalidation

src/cachified.spec.ts

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -590,6 +590,45 @@ describe('cachified', () => {
590590
expect(cache.get('weather')?.value).toBe('🌈');
591591
});
592592

593+
it('de-duplicates stale refreshes', async () => {
594+
const cache = new Map<string, CacheEntry>();
595+
const log: string[] = [];
596+
const backgroundTasks: Promise<unknown>[] = [];
597+
598+
const getValue = (id: string) => {
599+
return cachified({
600+
cache,
601+
waitUntil(p) {
602+
backgroundTasks.push(p);
603+
},
604+
key: `test`,
605+
ttl: 10,
606+
swr: 50,
607+
getFreshValue() {
608+
log.push(id);
609+
return id;
610+
},
611+
});
612+
};
613+
614+
// Warm up cache
615+
await getValue('1');
616+
617+
// These calls are background refreshing. Second one is de-duplicated.
618+
currentTime = 15;
619+
const call2 = getValue('2');
620+
const call3 = getValue('3');
621+
622+
// They resolve with stale value
623+
expect(await call3).toBe('1');
624+
expect(await call2).toBe('1');
625+
626+
await Promise.all(backgroundTasks);
627+
628+
// Only one refresh was executed
629+
expect(log).toEqual(['1', '2']);
630+
});
631+
593632
it('gets different values for different keys', async () => {
594633
const cache = new Map<string, CacheEntry>();
595634

src/common.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,8 @@ export interface CachifiedOptions<Value> {
184184
* Must be positive and finite
185185
*
186186
* Default: `0`
187+
* @deprecated manually delay background refreshes in getFreshValue instead
188+
* @see https://github.com/epicweb-dev/cachified/issues/132
187189
*/
188190
staleRefreshTimeout?: number;
189191
/**

src/getCachedValue.ts

Lines changed: 21 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -54,24 +54,28 @@ export async function getCachedValue<Value>(
5454
if (staleRefresh) {
5555
// refresh cache in background so future requests are faster
5656
context.waitUntil(
57-
Promise.resolve().then(async () => {
58-
await sleep(staleRefreshTimeout);
59-
report({ name: 'refreshValueStart' });
60-
await cachified({
61-
...context,
62-
getFreshValue({ metadata }) {
63-
return context.getFreshValue({ metadata, background: true });
64-
},
65-
forceFresh: true,
66-
fallbackToCache: false,
57+
cachified({
58+
...context,
59+
async getFreshValue({ metadata }) {
60+
/* TODO: When staleRefreshTimeout option is removed we should
61+
also remove this or set it to ~0-200ms depending on ttl values.
62+
The intention of the delay is to not take sync resources for
63+
background refreshing – still we need to queue the refresh
64+
directly so that the de-duplication works.
65+
See https://github.com/epicweb-dev/cachified/issues/132 */
66+
await sleep(staleRefreshTimeout);
67+
report({ name: 'refreshValueStart' });
68+
return context.getFreshValue({ metadata, background: true });
69+
},
70+
forceFresh: true,
71+
fallbackToCache: false,
72+
})
73+
.then((value) => {
74+
report({ name: 'refreshValueSuccess', value });
6775
})
68-
.then((value) => {
69-
report({ name: 'refreshValueSuccess', value });
70-
})
71-
.catch((error) => {
72-
report({ name: 'refreshValueError', error });
73-
});
74-
}),
76+
.catch((error) => {
77+
report({ name: 'refreshValueError', error });
78+
}),
7579
);
7680
}
7781

0 commit comments

Comments
 (0)