crazy4groovy
9/26/2018 - 3:43 PM

cache function objects, max size constraint

cache function objects, max size constraint

function cacheFunc(fn, { maxKeys, exposeCache }) {
  const cache = {} // cached thunks; would new WeakMap() be better? What "object" key would we use to test existence?

  const getThunk = (...args) => {
      const key = JSON.stringify(args)
      if (cache[key]) return cache[key]

      cache[key] = (...args2) => fn.call(this, ...args.concat(args2)) // cache[key] points to this new thunk

      if (maxKeys && (Object.keys(cache).length > maxKeys)) {
        const oldestKey = Object.keys(cache)[0] // note: Object.keys returns keys in the order they were added to cache object!
        delete cache[oldestKey]
      }

      return cache[key]
  }

  if (exposeCache) getThunk.cache = cache;

  return getThunk;
}

/*
var a = (x, y) => console.log(x * 2, y * 3)
var ax = cacheFunc(a, { maxKeys: 2 }) // cacheFunc.call(this, a, { maxKeys: 2, exposeCache: true})
var ax1 = ax(1) // cacheSize is 1
ax1() // logs 2, NaN
var ax2 = ax(1) // cacheSize is 1
ax2() // logs 2, NaN
var ax3 = ax(2) // cacheSize is 2
ax3() // logs 4, NaN
ax3(3) // logs 4, 9
var ax4 = ax(3) // cacheSize is 2 (max/trimmed)
ax4() // logs 9, NaN
Object.keys(ax.cache).length === 2 // if exposed cache
*/

/*
var callApi = id => fetch(`https://myapi.com/user/${id}`).then(r => r.json())
var callApiX = cacheFunc(callApi)
var users = [{ id: 1 }, { id: 2 }, ..... { id: 1 }, .....] // note: redundant id's
users.forEach((u) => { u.apiFetch = callApiX(u.id) }) // note: redundant id's will return their same cached function/thunk
users[0].apiFetch().then(...)
users[1].apiFetch().then(...)

Also useful for React components in render():
use:
  {users.map(user => ...... onClick={this.callApiX(user.id)} ......)}
instead of:
  {users.map(user => ...... onClick={() => this.callApi(user.id)} ......)}
*/