Wednesday, April 8, 2015

Simple Little Cacher

So a common pattern is caching. I have some functions to get pieces of data. These functions may have some expense in getting the data so I want to cache the data. But I also want to invalidate this cache at specific times. So I could do something like:


MyObject.prototype.myFunction = function() {
    if (this.myCache === undefined) {
        var result = ... my functionality here...
        this.myCache = result;
    }
    return this.myCache;
}


and when I want to invalidate the cache I do:

this.myCache = undefined;

This is fine, but what if you have many functions like this. You have a lot of repeated code and you have to remember to invalidate at the right time. Worse, your function is no longer about what it is supposed to be doing. Instead the code in your function is about caching! Here is a simple solution I came up with for a project I am working on. In this form it only handles functions that take no arguments, but theoretically it could be extended.

function CacheObject() {
    var self = this;
    self.nameCounter = 0;
    self.data = {};
    self.cacheFn = function(fn) {
        var cacheObject = self;
        var name = cacheObject.makeName();
        return function() {
            if (cacheObject.data[name] == undefined) {
                var cacheMe = fn();
                cacheObject.data[name] = cacheMe;
            }
            return cacheObject.data[name];
        }
    };
    self.makeName = function() {
        return self.nameCounter++;
    };
    self.clearCache = function() {
        self.data = {};
    };
}

Here is a fiddle where I developed this: http://jsfiddle.net/willseitz/oxcgnd13/. So the interesting thing about this is that is blends object references and closure references. The key into the cache exists only in the closure ("name"), but the actual cache ("data") exists in the object so that it can be cleared. In terms of use you can just do this:

var myCache = new CacheObject();
myFunction = myCache.cacheFn(myFunction);

and then

myCache.clearCache();

when needed. Now you can write your functions without thinking about caching. You don't need to pollute your objects with caching variables (besides one one cache object). Your objects and functions are about what they do and not caching. Yet you have a very nice caching object that you can independently test.

Addendum: In my actual implementation I pass in a context ("this") into the constructor and call the function with that context ("fn.call('context')").

No comments:

Post a Comment