Memoized Function with TTL

Lodash.memoize creates a function that memoizes the result of the function passed as a parameter. This is great if you want cache the result of an expensive function.

However, lodash.memoize doesn’t support a TTL or expiry, but it does let you customise the underlying cache it uses.

So, if for example you want to cache the responses you are retrieving from an API heres a simple way to add TTL functionality to memoize:

import memoize from 'lodash.memoize';

const DEFAULT_CACHE_EXPIRY = 60000;

// create a custom cache based on Map
export default class CustomCache extends Map {
  constructor() {
   super();
  this.expiry = DEFAULT_CACHE_EXPIRY;
  }

  has(key) {
    // when checking the cache also check the expiry
    return super.has(key) && super.get(key).expiry > Date.now();
  }

  set(key, value) {
    // when setting the value, add an expiry property
    value.expiry = Date.now + this.expiry;
    return super.set(key, value);
  }
}

// provide the custom cache for memoize to use
memoize.Cache = CustomCache;

// example function fetching data from the
const fetchData = async () => {
  return fetch(`https://someapi-example-api`)
  .then((response) => response.json())
  .then((response) => response.data);
}

// create a memoized function
const cachedFetchData = memoize(fetchData);


// set the cache expiry
cachedFetchData.cache.expiry = 30000

...

// later, call the memoized function as follows
const response = await cacheFetchData();

A custom cache can be created that extends the Map type.

An expiry property is added to the cached value in the set method before saving it in the Map.
The has method then checks the expiry of the item if it exists in the cache.

We can then set memoize.Cache to our custom cache implementation.

When we wrap the function we want to cache, memoize exposes the .cache property that can be used to set the desired TTL for the cached items.

You can then call the cached function as needed. If the value is in the cache and not expired, it will be returned, otherwise the inner function will be called.

Caveats:

  • This approach will work if the value returned from your function is an object. You would have to extend the approach to support scalar values.
  • By adding the expiry in this way property you are mutating the object, which is a bit yucky and could have side effects. This could be extended to be more functional, but hey, I said “simple approach”.