Skip to content

Instantly share code, notes, and snippets.

@NikhilVerma
Last active September 10, 2020 11:39
Show Gist options
  • Save NikhilVerma/eeb941a282c0113e092e34e54475afaf to your computer and use it in GitHub Desktop.
Save NikhilVerma/eeb941a282c0113e092e34e54475afaf to your computer and use it in GitHub Desktop.
/**
* Simple implementation of LRU + SWR cache approaches to enable fast cache retrieval at the same time allowing refresh in the background
*/
import LRUCache from 'lru-cache';
const lruCache = new LRUCache<string, CacheFormat<any>>({
max: 1000,
maxAge: Infinity,
// These two options are reduntant since we cache forever, but if we decide to change it
// they will be helpful
stale: true,
updateAgeOnGet: true,
});
type CacheFormat<T> = {
cacheAge: number;
data: T;
};
const IN_PROGRESS: Record<string, Promise<any> | null> = {};
export type GetSwrProps<T> = {
key: object | string;
timeout?: number;
fetch: () => Promise<T>;
};
/**
* Returns a stale version of item while it refreshes cached entries in the background
*/
export async function getStaleWhileRefresh<T>({
key,
fetch,
timeout = 1000 * 10,
}: GetSwrProps<T>): Promise<T> {
const cacheKey = JSON.stringify(key, null, 0);
const cachedItem = lruCache.get(cacheKey);
// Prevent multiple requests
if (!cachedItem && IN_PROGRESS[cacheKey]) {
return IN_PROGRESS[cacheKey];
}
if (cachedItem) {
// We hit a cached item, refresh it if beyond cache age
if (Date.now() - (cachedItem.cacheAge + timeout) > 0) {
refreshCache();
}
return cachedItem.data;
}
refreshCache();
function fetchAndSet() {
const now = Date.now();
return fetch()
.then((data) => {
lruCache.set(cacheKey, {
cacheAge: Date.now(),
data,
});
IN_PROGRESS[cacheKey] = null;
delete IN_PROGRESS[cacheKey];
console.log(
'[swr-cache] Refreshed cache for',
cacheKey,
'time taken',
`${Date.now() - now}ms`
);
return data;
})
.catch((err) => {
IN_PROGRESS[cacheKey] = null;
delete IN_PROGRESS[cacheKey];
console.error(`[swr-cache] Failed to update cache for ${cacheKey}`, err);
throw err;
});
}
function refreshCache() {
// If we don't have a fetch already in progress then do one
if (!IN_PROGRESS[cacheKey]) {
IN_PROGRESS[cacheKey] = fetchAndSet();
}
}
return IN_PROGRESS[cacheKey];
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment