Memoization is an optimization technique for improving the execution speed of computer programs. Saves computation time by storing the return value of a computationally expensive function and extracting the result from the cache when it is needed again without having to compute it again.

Memorization is a typical approach to balancing computing time with memory space.

Source: Wikipedia

As you can see from the above definition, caching is a very useful technique to store the results of a function for return the next time it is called. I’m not talking about what scenarios to use caching but how to write a reusable Memoization function

MemoizationSync

MemoizationSync is literally a synchronized version of Memoization. How do you write a generic MemoizationSync to cache the results

const memo = {};

const add = (. args) = > {
  const key = args.join();
  if (Object.prototype.hasOwnProperty.call(memo, key)) {
    return memo[key];
  }
  const result = args.reduce((a, b) = > a + b, 0);
  memo[key] = result;
  return result;
};

add(1.2.3);
add(1.2.3);
Copy the code

If you were to add a caching function to the add function display, you might write it as above, but it has problems.

This function does two things at once

  • Calculate the args
  • Reading the memo cache but displaying it in this way violates the single principle of design pattern, and will cause trouble for subsequent maintenance. And if we need a new function cache, will we have to make a copy of the past? Obviously, it does not meet our requirements
const add = (. args) = > {
  return args.reduce((a, b) = > a + b, 0);
};

const memoizationSync = (fn, key) = > {
  const memo = {};
  return function callback(. args) {
    if (memo.hasOwnProperty(key)) {
      return memo[key];
    }
    const result = fn.apply(this, args);
    memo[key] = result;
    return result;
  };
};

const argumentsArr = [1.2.3];
constmemoizationAdd = memoizationSync(add, argumentsArr); memoizationAdd(... argumentsArr); memoizationAdd(... argumentsArr);Copy the code

This results in a generic memoizationSync function that takes a key and uses it for discriminating caching.

However, we need to modify it to be flexible and suitable for more scenarios. Let’s see what this function does before we modify it

  • Read the cache based on the key
  • No cache to perform the function set cache obviously these two steps are not going to change, but we cankeyTo improve thekeyWe want it to be a function and have default values, so that in complex scenarios the user can specify which fields to cache based on parameters
const memoizationSync = (fn, getKey = (... args) => args.join(' ')) = > {
  const memo = {};
  return function callback(. args) {
    const key = typeof getKey === 'function' ? getKey.apply(this, args) : getKey;
    if (memo.hasOwnProperty(key)) {
      return memo[key];
    }
    const result = fn.apply(this, args);
    memo[key] = result;
    return result;
  };
};
Copy the code

MemoizationAsync

The asynchronous scenario in JavaScript can be roughly divided into two parts

  • The callback function
  • Promise (Async, Generator are both based on promises)

So let’s talk about these two parts how do we implement caching

Callback

I now have a getImgSize function that returns the true width and height of the image based on img SRC, and if successful calls callback to pass the parameters (node callback style). But I want to return if the URL is the same instead of calling getImgSize again

const getImgSize = (src, callback) = > {
  const img = new Image();
  img.src = src;
  img.addEventListener('load'.() = > {
    callback(null, {
      width: img.naturalWidth,
      height: img.naturalHeight,
    });
  });

  img.addEventListener('error'.(e) = > {
    callback(new Error(e), null);
  });
};

const url = 'https://www.google.com/images/branding/googlelogo/1x/googlelogo_color_272x92dp.png';

getImgSize(url, (error, size) = > {
  if (error) {
    return;
  }
  console.log(size);
});
Copy the code

Before we write memoizationAsync we want to think about how to write this function. As a general rule we want to not have too much impact on how users use it, so the expected call is as follows

const getImgSizeMemoization = memoizationAsync(getImgSize);
getImgSizeMemoization(url, (error, size) = > {
  if (error) {
    return;
  }
  console.log(size);
});
// Use cache for the same parameters
getImgSizeMemoization(url, (error, size) = > {
  if (error) {
    return;
  }
  console.log(size);
});
Copy the code

The next step is to write this function

const memoizationAsync = (fn, getKey = (... args) => args.join()) = > {
  const memo = {};
  const queue = {};
  return function (. rest) {
    const callback = Array.prototype.pop.call(rest);
    const key = getKey.apply(this, rest);
    const cb = (. args) = > {
      memo[key] = args;
      queue[key].forEach((item) = > {
        item.apply(this, args);
      });
      delete queue[key];
    };
    if (memo.hasOwnProperty(key)) {
      return memo[key];
    }
    if(! queue.hasOwnProperty(key)) { queue[key] = [callback]; }else {
      queue[key].push(callback);
      // Wait for the first execution to complete
      return;
    }
    fn.apply(this, [...rest, cb]);
  };
};
Copy the code

This function is designed to create a custom cb callback function for record caching and call user-passed callbacks. The other read and set caches are the same as above.

The reason for the queue variable is that the asynchronous task may be requested when it is added, but the memo has not been written to the result. Therefore, the request is managed through the queue, and the queue tasks are executed in batches after the first request finishes, and then deleted.

Promise

Recall the features of the Promise function

  • The status of an object is not affected
  • Once the state changes, it never changes again, and you can get this result at any time

The second point is, unlike the callback function, it can be called at any time as soon as the result comes out, so we’re listening for an event at click and if we miss that listener it’s impossible to backtrack, but the promise call that you repeated a thousand times. Then, Based on this feature we write MemoizationAsync

const getImgSize = (src) = > {
  return new Promise((resolve, reject) = > {
    const img = new Image();
    img.src = src;
    img.addEventListener('load'.() = > {
      return resolve({
        width: img.naturalWidth,
        height: img.naturalHeight,
      });
    });

    img.addEventListener('error'.(e) = > {
      return reject(e);
    });
  });
};

const url = 'https://www.google.com/images/branding/googlelogo/1x/googlelogo_color_272x92dp.png';

const memoizationAsync = (fn, getKey = (... args) => args.join()) = > {
  const memo = {};
  return async function (. args) {
    const key = getKey.apply(this, args);
    if (memo.hasOwnProperty(key)) {
      return memo[key];
    }
    const result = fn.apply(this, args);
    memo[key] = result;
    return result;
  };
};

const getImgSizeMemoization = memoizationAsync(getImgSize);

(async() = > {const result = await Promise.all([getImgSizeMemoization(url), getImgSizeMemoization(url)]);
  console.log(result); }) ();Copy the code

MemoizationAsync is very compact and almost the same as the synchronous version, except that we just return the Promise state and thanks to the Promise feature this function will return the correct result at any time

The last

If it is helpful to you, you can pay attention to it and star it. What mistakes are welcome to point out