JavaScript caching function return values

As web applications get more complex, more and more of the processing work is pushed to the client browser in the form of JavaScript code. We seasoned web developers earned our stripes in the server-side world, where our own web server executed the code. With this direct tie between a web browser and our precious server resources, caching was relied upon heavily, not only to improve site performance, but to reduce server load, which directly correlates to server operation costs. We had those two motivations to leverage caching.

With the client-side revolution though, the web developer is seeing a decrease in server load, and an increase in client (browser) load. Sounds like a dream come true. But, this leads back to the motivation for performance. We no longer need to rely on our own server resources for performance, but instead pass the buck onto the user's computer processing power.

The issue isn't solely tied to developer motivation, but also to client-side language support. JavaScript doesn't offer a built-in caching solution. So, if we have a function which only expects 3 or 4 unique argument values over the script's lifetime, and that function happens to take 5 seconds to process, we're not doing our duty as programmers to put caching to work for the user. After all, we're still going to use the client's memory for caching, what do we have to lose!

Implementing a function return value cache only takes a few lines of code. Below is pseudocode that is syntactically correct JavaScript. I call it pseudocode because it really doesn't deal with all of the requirements that a proper cache should have (such as invalidating, lifetime, maximum size, etc). But, it introduces the concept and provides a foundation.

function Cache() {
    // create the cache object as a singleton (only one instance allowed)
    if(typeof Cache.instance === 'undefined') {
        Cache.instance = this;
    }

    var data = [ ]

    // we'll abbreviate cacheAwareCall as caCall
    this.caCall = function(functionName) {
        var cacheCheck = this.load(functionName, this.caCall.arguments);

        if (typeof cacheCheck !== 'undefined') {
            return cacheCheck;
        else {
            var returnValue = window[functionName].apply(this, this.caCall.arguments)
            this.save(functionName, this.caCall.arguments, returnValue);
            return returnValue;
        }
    }

    this.save = function(functionName, argumentObject, returnValue) {
        // prepend item to cache
        data.unshift({ fname: functionName, arguments: argumentObject, returnValue: returnValue });
    }

    this.load = function(functionName, argumentObject) {
        for(entry in data) {
            if(data[entry]['fname'] === functionName) {
                // we have a match on the function name
                // deepCompare is not implemented here, examples are throughout the web
                if(deepCompare(argumentObject, data[entry]['arguments']) {
                    return data[entry]['returnValue'];
                }
            }
        }

        return undefined;
    }

    return Cache.instance;
}

Why follow me on Twitter?

  • I tweet about new technologies, services or libraries I find interesting
  • Yeah, sometimes I'll post a pet-peeve or rant about something trivial
  • If I discover something that made my web development life easier, I share it
  • I'll shout out any handy tip that I think might be useful to other devs
  • Very rarely anything promotional


Tagged , , .

Updated: 2012-10-21

Phil LaNasa follow us in feedly