Memoize

🧠

function anExpensiveCalculation(parameters) {
  /**
   * Code that takes a while to execute 🕰.
   * Once the calculation is made, you can return it
   * Why doing it again?
   */
}

function sort(manyEntries) {
  /**
   * Sort many entries can take a while ⏳.
   * Once the data is already sorted, it would be great to 
   * remember how it was sorted to avoid doing it again.
   */
}

function getProductById(id) {
  /** 
   * You get a product from an API given its Id
   * The API is a bit slow 🐢.
   * Once you already have the product, you can
   * remember it to avoid asking it again, if you don't care 
   * about data freshness.
   */
}
function add(a, b) {
  console.log(`Calculates the sum with ${a} and ${b}`);
  return a + b;
}

const result1 = add(3, 4);
const result2 = add(3, 4);

console.log("result 1: " + result1);
console.log("result 2: " + result2);

// Calculates the sum with 3 and 4 
// Calculates the sum with 3 and 4 
// result 1: 7 
// result 2: 7 

First implementation

let result = undefined;

function add(a, b) {
  if (!result) {
    console.log(`Calculates the sum with ${a} and ${b}`);
    result = a + b;
  }

  return result;
}

const result1 = add(3, 4);
const result2 = add(3, 4);

console.log("result 1: " + result1);
console.log("result 2: " + result2);

// Calculates the sum with 3 and 4 
// result 1: 7 
// result 2: 7 

Remembering the previous result

function addWithMemory() {
  let result = undefined;

  return function add(a, b) {
    if (!result) {
      console.log(`Calculates the sum with ${a} and ${b}`);
      result = a + b;
    }

    return result;
  };
}

const memoAdd = addWithMemory();

const result1 = memoAdd(3, 4);
const result2 = memoAdd(3, 4);

console.log("result 1: " + result1);
console.log("result 2: " + result2);

// Calculates the sum with 3 and 4 
// result 1: 7 
// result 2: 7 

Encapsulating the memory variable

const result1 = memoAdd(3, 4);
const result2 = memoAdd(2, 1);

console.log("result 1: " + result1);
console.log("result 2: " + result2);

// Calculates the sum with 3 and 4 
// result 1: 7 
// result 2: 7 💩

Supporting executions with different parameters

function addWithMemory() {
  let dictionary = {};

  return function add(a, b) {
    const key = `${a}_${b}`;

    if (!key in dictionary) {
      console.log(`Calculates the sum with ${a} and ${b}`);

      dictionary[key] = a + b;
    }

    return dictionary[key];
  };
}

Supporting executions with different parameters

const memoAdd = addWithMemory();

const result1 = memoAdd(3, 4);
const result2 = memoAdd(3, 4);
const result3 = memoAdd(2, 1);
const result4 = memoAdd(2, 1);

console.log("result 1: " + result1);
console.log("result 2: " + result2);
console.log("result 3: " + result3);
console.log("result 4: " + result4);

/*
> Calculates the sum with 3 and 4 
> Calculates the sum with 2 and 1 
> result 1: 7 
> result 2: 7 
> result 3: 3 
> result 4: 3 
*/

Supporting executions with different parameters

function add(a, b) {
  return a + b;
}

/**
 * memoAdd is a composition of two behaviours:
 * add and memoize
 */
const memoAdd = memoize(add);

Extract responsibilities with function composition

function memoize(fn) {
  let dictionary = {};

  return function (...args) {
    // It generates the key like in previous steps, but
    // in this case it allows any number of parameters.
    const key = args.join("_");

    // If the key is found in the dictionary, it returns
    // the value from a previous execution.
    if (key in dictionary) {
      console.log("Returns the stored result in the dictionary");
      return dictionary[key];
    } else {
      // Executes the fn function by passing the parameters
      const result = fn(...args);

      // Stores the result in the dictionary
      dictionary[key] = result;
      return result;
    }
  };
}

export default memoize;

Extract responsibilities with function composition

// add.js

import memoize from "./memoize";

const add = memoize(function add(a, b) {
  return a + b;
});

export default add;

Extract responsibilities with function composition

// index.js
import add from "./add";

const result1 = add(3, 4);
const result2 = add(3, 4);
const result3 = add(2, 1);
const result4 = add(2, 1);

console.log("result 1: " + result1);
console.log("result 2: " + result2);
console.log("result 3: " + result3);
console.log("result 4: " + result4);

/*
Calculates the sum with 3 and 4
Calculates the sum with 2 and 1
Returns the cached result 
Returns the cached result
result 1: 7 
result 2: 7 
result 3: 3 
result 4: 3 
*/

Extract responsibilities with function composition

const getPermissions = memoize(async function(userId) {
  const result = await fetch(`${host}/users/${userId}/permissions`)
  const permissions = await result.json();
  return permissions;
})

export default getPermissions;

Real use cases

const getTranslations = memoize(async function(culture) {
  const result = await fetch(`${host}/translations/${culture}`)
  const permissions = await result.json();
  return permissions;
})

export default getTranslations;

Takeaways

  • Use memoize to easily save expensive executions in memory *
    • * If it's an API call, make sure you don't need data freshness
  • You might not need a library or a global state architecture to cache things in memory
  • Further reading:

deck

By Daniel de la Cruz Calvo

deck

  • 164