In the final part of the series we'll be adding expiry to cached values.
Breakdown:
- Just like the cacheSize we'll accept an expiresAt param with value in ms.
- If it's present then each node should store a timestamp of when it was created
- after finding a node we have to check if it expired
- clean expired nodes
Update cache options
let options = { cacheSize: DEFAULT_CACHE_SIZE, expiresAt: null, ...params, };
Update Node structure
function Node(key, value, expires) { this.key = key; this.value = value; this.next = null; this.prev = null; this.timestamp = expires ? Date.now() : null; }
Add a function to check expiry
Node.prototype.hasExpired = function (diff) { if (diff && this.timestamp) { return Date.now() - this.timestamp >= diff; } };
Pass expires flag when creating a new node
// inside add function const node = new Node(key, value, options.expiresAt);
Update find function to ignore an expired node
// check for cached node function find(key) { if (key in hash) { const node = hash[key]; if (!node.hasExpired(options.expiresAt)) { refresh(node); return node; } // TODO: remove expired node } return null; }
Time for some tests,
(async function () { // expires after one second const testCache = Cache({ cacheSize: 3, expiresAt: 1000 }); testCache.add("1-2", 3); testCache.add("2-3", 5); testCache.add("5-5", 10); testCache.add("4-2", 6); console.log(testCache.find("2-3")); // returns Node // wait for 2 seconds await new Promise((r) => setTimeout(r, 2000)); console.log(testCache.find("2-3")); // returns null })();
Find returned null for "2-3" because it expired after one second.
To remove expired nodes we have to modify the remove function to remove any node passed to it rather than just the tail node.
function remove(node) { if (node) { delete hash[node.key]; // if the node is in the middle if (node.prev) { node.prev.next = node.next; } if (node.next) { node.next.prev = node.prev; } // if it's the tail node if (node === tail) { tail = node.prev; } // if it's the head node if (node === head) { head = node.next; } size--; } }
Also update the existing call in the add
function to remove(tail)
Update the find function to remove expired nodes
function find(key) { if (key in hash) { const node = hash[key]; if (node.hasExpired(options.expiresAt)) { remove(node); } else { refresh(node); return node; } } return null; }
Update the test above, add a print at the end
console.log(testCache.print()); // output: "[4-2: 6] -> [5-5: 10]"
Referring an expired node removed it from the linked list. The cache is working, let's test the memoizer
(async function () { let count = 0; function add(a, b, c = 0) { count++; return a + b + c; } const memoAdd = memoizer(add, { cacheSize: 3, expiresAt: 1000 }); memoAdd(5, 3); memoAdd(3, 3); memoAdd(1, 2); memoAdd(2, 4); console.log(count); // 4 await new Promise((r) => setTimeout(r, 2000)); memoAdd(1, 2); console.log(count); // 5, cache expired memoAdd(1, 2); console.log(count); // 5, pulled from cache memoAdd(2, 4); console.log(count); // 6, expired value })();
Works as expected, we are done with a good enough implementation of a memoizer function with support for cacheSize and expiry.
The memoizer code and jest tests can reviewed here
That's all folks :)
Photo by Steve Johnson on Unsplash
Top comments (0)