Web Performance Calendar

The speed geek's favorite time of year
2014 Edition
ABOUT THE AUTHOR

Chris Love (@ChrisLove) has over 20 years, yes that's right, of web development experience. He has built a wide variety of web sites and applications in those years. In recent years he has begun to immerse himself in the modern, single page web application space. This has giving him some amazing experiences using HTML5, JavaScript, CSS and responsive design. Currently he is obsessed with modern web and mobility to help solve the problems many enterprises are having adapting to the rapidly changing technology landscape. He has authored 3 books, including his latest, High Performance Single Page Web Applications. He is a 7 time ASP.NET MVP, ASP Insider and Internet Explorer User Agent. Chris regularly speaks at user groups, code camps and other developer events

One of the cardinal rules for web performance is to reduce HTTP requests. The common interpretation of reducing HTTP requests limits the focus to bundling and minifying scripts, creating image sprites and eliminating unused resources. Developers often overlook AJAX requests. Many AJAX calls GET the same, unchanged data as previously made requests. Today’s rich web applications, like single page applications, rely on numerous AJAX calls to retrieve data before it is rendered. Untamed these AJAX requests create unnecessary chatter between client instances and the server.

All modern browsers provide at least 2 ways to cache and persist data locally, localStorage and IndexDB (WebSQL is still around, but has become deprecated). Browser storage provides a mechanism to cache data, allowing us to avoid many costly HTTP requests. Eliminating these requests makes our applications perform much faster and helps server scale.

A few years ago Paul Irish publish an example using localStorage and jQuery’s ajaxPreFilter mechanism that caches AJAX GET request results. Paul’s ajaxPreFilter checks to see if the desired data exists in localStorage before the request to the server is made. If the data exist the preFilter cancels the AJAX call and executes the callback, passing the cached data to the callback. If the data is not cached locally the AJAX call is made and the response data is cached in localStorage for future requests.

The ajaxPreFilter is a simple, but effective mechanism to reduce AJAX calls. Production applications need various levels of sophistication because each piece of data needs different times to live (TTL) or may not need to be cached at all. When designing a local caching architecture developers should account for the application’s data needs.

The ajaxPreFilter is a great way to hook into the jQuery AJAX pipeline, but more and more applications do not rely on jQuery. In addition having a common caching library allows developers to reuse local caching across various data service modules and applications in a framework agnostic manner. A common module I use implements a known interface of 4 functions:

  • setItem(key, value, ttl)
  • setObject(key, value, ttl)
  • getItem(key)
  • getObject(key)

The setObject and getObject functions call the setItem and getItem functions internally, but abstract the JSON.stringify and JSON.parse functions because this functionality is repeated in many applications. The setItem function adds the ‘value’ to localStorage using the ‘key’ as the index. An additional value is added to localStorage, a time to live value if a ttlKey value has been provided to the cache module.

The ttlKey value is used to test if the data is stale. The value is a timestamp millisecond value and is set to a time in the future. The value I am using in this article’s example is 24 hours or 86400000 milliseconds. If the request time is made after the TTL time has passed the cache library returns null and the stale data is removed from localStorage.

The following code block demonstrates how to use a cache service to retrieve locally stored data or make an AJAX call when a local instance of the data is not available. The code uses the reqwest library to make AJAX calls and is part of larger module with values defined elsewhere. The example uses the Rotten Tomatoes API to retrieve a list of movies opening this week.

getNewMovies: function (callback) {
 
  var movie = this,
      url = rtRoot + "lists/movies/opening.json?apikey=" +
            apiKey + "&page_limit=" +
            defaultPageLimit + "&page=1",
      // use cache provider to see if the data is available
      cached = movie.cache.getObject(newMovies);
 
  // if a local version is available use it and make the callback
  if (cached) {
    if (callback) {
      callback(cached);
    }
 
  } else {
    // no local version of the data is available, make the ajax call
    reqwest({
      url: url,
      type: "jsonp"
    })
    .then(function (resp) {
 
      // store data to local Cache
      movie.cache.setObject(newMovies, resp.movies, moviesTTL);
 
      // make success callback
      if (callback) {
        callback(resp.movies);
      }
     })
     .fail(function (e) {
 
       console.error("just look at what you have done!");
 
     });
 
  }
 
},

Using the developer tools you can check localStorage before an initial request is made. Here there are no values stored in localStorage:

Loading the page causes an AJAX request to be made:

Checking localStorage again shows we have the list of movies and a corresponding TTL value cached in localStorage:

Checking a 2nd page load we see there is no AJAX call made to retrieve the freshly cached data:

As you can see there is no additional request made to load the unchanged data. The client experience is virtually instant because the data is being retrieved from within the browser. Because the data is stored in localStorage it can also persist across sessions and does not increase the application’s memory footprint. If data needs to be purged when a user session ends it can be stored in sessionStorage instead. The TTL key gives the data a life cycle, causing the data to eventually become stale. When the data is stale a request to the server is made, retrieving fresh data to be cached locally.

Caching data locally not only creates faster applications, but applications that scale better. When the server does not need to respond to excess API calls it can process other calls more efficiently. It also means online services do not need as many servers. This reduces business overhead because hosting fees are lower and IT does not need to respond to as many scaling issues.

This example focuses on localStorage, but sessionStorage and IndexDB could be used. The localStorage caching library is configured to use either localStorage (default) or sessionStorage. A sibling library could be substituted that implemented the getObject and setObject functions that uses IndexDB as a data store.

You can download the localStorage service and example code used in this article from GitHub. You can find more details implementing localStorage AJAX caching and other high performance web development techniques in my High Performance Single Page Web Page Applications book.