Using Promises to Cache Static AJAX JSON Data

This article showcases a useful caching strategy for static data that is fetch via AJAX. We will use jQuery to setup a promise and cache the data in the localStorage for subsequent page loads or data loads.

Getting ready

A modern web browser supporting localStorage and JSON. Also, a basic understanding of promises[2] is helpful.

How do it…

Here is the code:

(function($) {
  var oKeyDeferredMap = {};
  
  function fnReadData(sKey) {
    var sValue = window.localStorage.getItem(sKey);
    return sValue ? JSON.parse(sValue) : sValue;
  }
  
  function fnWriteData(sKey, oData) {
    var sValue = JSON.stringify(oData);
    window.localStorage.setItem(sKey, sValue);
  }
  
  $.cachedAjaxPromise = function(sUrl, oAjaxOptions) {
    var oDeferred = oKeyDeferredMap[sUrl];
    var sValue;
    
    if (!oDeferred) {
      oDeferred = new jQuery.Deferred();
      oKeyDeferredMap[sUrl] = oDeferred;
      sValue = fnReadData(sUrl);
      
      if (sValue) {
        oDeferred.resolve(sValue);
      }
      
      if (!oAjaxOptions) {
        oAjaxOptions = {};
      }
      
      $.extend(oAjaxOptions, {
        error: function(oXHR, sTextStatus, sErrorThrown) {
          console.log('customer info request failed: ' + sErrorThrown);
          oDeferred.resolve(null);
        },
        success: function(oData) {
          // making assumption that data is JSON
          fnWriteData(sUrl, oData);
          oDeferred.resolve(oData);
        }
      });
      
      $.ajax(sUrl, oAjaxOptions);
    }
    
    return oDeferred.promise();
  };
}(jQuery));

Here is how to use the code:

oPromise = $.cachedAjaxPromise('/someURL').done(function(o) {
  console.dir(o);
});

Here is a codepen demoing the code:

See the Pen HtJcD by Matt Snider (@mattsnider) on CodePen.


How it works…

The code snippet adds a new function to jQuery cachedAjaxPromise which should be passed a URL returning a static JSON and returns a promise that will be resolved with the JSON object. The function checks the local storage for a value stored at the key of the url. If a value exists, then resolve the promise. If the value doesn’t exist, then fetch it from the server, cached into localStoage, and the promise is resolved or rejected based on the AJAX response cycle. All cached values are marshalled and unmarshalled using the JSON infrastructure to and from strings. Lastly, the jQuery deferred object, is also cached, to prevent duplicate AJAX requests or calls to the localStorage when promises for the same url are created.

To use cachedAjaxPromise, provide a url and chain a done or then function. The success callback provided will be pass the JSON data. If you look at the example pen, you will see that the first time the pen is loaded (or after you clear the localStorage) it takes about two seconds (simulated AJAX request), but subsequent page loads resolve the data in millisecond (from localStorage). You may pass a second argument, as the configuration options to pass into $.ajax, but the success and error functions will be overwritten for use with the promise system.

This a very simple example that is dependent on modern web browser technology. If you need to support legacy browsers, then you may need a polyfill the JSON[3] library and localStorage[4].

References

  1. jQuery Deferred
  2. Promises in Wicked Detail
  3. jQuery JSON Polyfill
  4. PersistJs - a localStorage Polyfill

jQuery Function for Change Event and Delayed Keydown Event

In my experience, it is rare to assign only a change event to a text input, as any callback that would be executed for the change event should also be called on a key event as well, but with a slight delay (think how an autocomplete shows results as you type). This is a common pattern and I was surprised to not immediately find a jQuery plugin implementing it, so I decided to add one ...

Hoisting 102 - Examining a Global Context Hoisting Gotcha

In an earlier article we covered Variable Hoisting in JavaScript. At the time, I did not expect to face a hoisting related code bug so quickly. And given the circumstances of the bug, I expect it to be one of the most common hoisting problems, so I wanted to share it.

How do it…

Let’s jump right into the setup. You have a function that is defined in one JavaScript file (this file ...

jQuery Widget for Dynamic Input Helper Text

This is a proof of concept widget that I demoed for work. The desire is to update some text according to a regex and replacement, when an input field changes. This will allow developers to show a message and/or format the input value, so users understand they do not need to enter meta characters and see the result of their input (think phone or identification numbers). I built a simple jQuery plugin that can be ...

Using PhantomCSS for Regression Testing Your CSS

I finally had a chance to play with PhantomCSS, a CSS regression testing tool that compares screenshots to see if CSS changes have affected the design, and wanted to share my experience with you. We are using it at Ariba to validate that I do not break any designs as I migrate us from two giant, unmanageable CSS files into smaller, modularize and better written lessCSS files. PhantomCSS uses

Rotating Multi-State Button

One of the designers at Ariba, recently came up with a design that needed a tri-state button to govern toggling some content. We came up with an interesting solution where the button has three colors on the outside, with the bottom color governing the content being shown. I am not sure if the widget will survive a usability study, but I put together a proof of concept using HTML 5 technologies and wanted to share ...

Variable Hoisting in JavaScript

The JavaScript Variable Scope article covered the basics of variable scope in JavaScript. Today I’d like to continue that discussion, exploring a more complicated concept called hoisting, where the names of variables declared inside of a function are hoisted to the front of the function, so that they may be accessed, even before they are defined.

How do it…

Here is a simple example of hoisting:
 function foo() { alert(a); var ...

JavaScript Variable Scope

JavaScript scopes variables in a unique way that often confuses developers coming from other languages. First, as discussed in the Context Binding Functions article, the this keyword is dynamic, and second, as we᾿ll discuss today, variable scopes change depending on how you declare them.

How do it…

The most basic way to declare a variable is:
 var foo1 = 'bar'; 
The variable foo1 will be added to the current execution ...

Context Binding Functions

I realized this week that I have never dedicated an article to context (scope) binding functions in JavaScript and thought I would remedy that today. JavaScript context binding functions are functions that guarantee the execution context of a callback function, allowing the developer to control what this is set to.

How do it…

We’ll look at two types of binding functions. The first, seamlessly wraps an inner callback function with a provided context, so ...

Multi Tab/Window Messaging in JavaScript

I recently implemented a JavaScript only communication system for sending messages between windows/tabs (just referred to as window for the remainder of this document) for Ariba.com. We have a very heavy-handed approach that requires identifying each window and queueing messages in the order that are sent, no matter the originating window. And the final kicker is that it needs to work on some older browsers. We settled on a shared communication channel using ...