Cache async results with a TTL
Config, feature flags and the current user are read constantly and change rarely. Caching them for a few seconds removes most of the requests, as long as concurrent callers share one load and errors are not remembered.
Implement createCache(loader, ttl), returning an object with get(key) and invalidate(key). On a miss, get calls loader(key) and returns its promise. While that load is in flight, further get calls for the same key return the same promise without calling loader again. Once it fulfils, the value is served for ttl milliseconds measured from when it fulfilled; after that the next get loads again. If the loader rejects, the rejection is passed to the waiting callers and nothing is stored, so the next get retries. invalidate(key) drops any entry for that key so the next get reloads.
Arguments
loader(key: any) => Promise<T>- Called on cache miss to fetch the value for
key. ttlnumber- How long a fulfilled value stays fresh, in milliseconds.
Returns
{ get: (key: any) => Promise<T>, invalidate: (key: any) => void } — A cache with a get that returns the cached (or loading) promise and an invalidate that drops an entry.
Examples
let calls = 0;
const cache = createCache(async (key) => {
calls++;
await new Promise((r) => setTimeout(r, 20));
return "value:" + key;
}, 1000);
const [a, b, c] = await Promise.all([
cache.get("x"), cache.get("x"), cache.get("x"),
]);
// a === b === c === "value:x", calls === 1 — one load shared by three callersConcurrent callers for the same key share a single load.
What it has to do
- Call
loaderonce for a key and reuse the value until the TTL expires. - Share one in-flight load between concurrent
getcalls for the same key. - Reload after the TTL has elapsed.
- Never cache a rejection: the next
getmust callloaderagain. - Make
invalidate(key)force the nextgetto reload.
Notes
- Store the promise (not the resolved value) so in-flight loads can be shared — callers that
getduring the load join the same promise. - Never cache a rejection: delete the entry in the error handler so the next
getretries.
Stuck? The javascript course covers everything this challenge needs.
- 1.serves the cached value within the TTL
- 2.shares one load between concurrent callers
- 3.reloads once the TTL has expired
- 4.does not cache a rejection
- 5.invalidate forces the next get to reload
These assertions run in your browser the moment you hit Run. Pass all of them to claim the XP.
Discussion
Sign in to postNo comments yet. Be the first to say something.