An AJAX Caching Strategy
by Bruce Perry
|
Pages: 1, 2, 3
Prototype's Ajax Object
The AJAX-related code using Prototype's Ajax.Request object is only about ten lines long. The code connects with a server, gets a new harvested energy price from the U.S. government, then displays the price without changing any other part of the web page. The code is, however, somewhat confusing to look at. Here's an explanation.
You are probably used to seeing the XMLHttpRequest object being programmed directly. Prototype, however, instantiates this object for you, including dealing with the different browser types the users will show up with. Just creating the new AJAX object, as in new Ajax.Request, sets the HTTP request in motion. The prototype.js file includes the definition of this object. Since our web page imported that JavaScript file, we can create new instances of this object without jumping through any other hoops. Our code doesn't have to deal with the various browser differences involving XHR.
The first parameter to Ajax.Request is the URL of the server component it is connecting with. For example, http://www.eeviewpoint.com/energy.jsp. The second parameter specifies that this is a GET HTTP request type. The third parameter provides a querystring that will be passed to the server component. The entire URL, therefore, might look like http://www.eeviewpoint.com/energy.jsp?priceTyp=oil for example.
Progress Indicator
A couple of other parameters let the code developer decide what the application should do at different stages of the request processing. For example, onLoaded represents a stage of the request sequence when the XMLHttpRequest.send() method has been called, and some HTTP response headers are available, as well as the HTTP response status. At this stage, our application displays the text "waiting..." where the new price will soon appear. onComplete represents the finished stage of the energy-price request. The function that is associated with onComplete, therefore, determines what happens with the request's return value. The code automatically provides the function with a request parameter that represents the instance of XHR used for the HTTP request.
onComplete:function(request){...
Therefore, in this case, the code gets the return value with the syntax request.responseText. If everything goes well, this property will be the new price, such as 66.07.
The Ajax.Request object also allows the code to take action based on certain HTTP response status values. This is accomplished by checking the status property of XHR (request.status). If the status is not 200 (it could be 404 meaning "Not Found" for example), a value signifying that the request was successful, then the application displays "unavailable" in the space where the price should be.
We could change this behavior so that an unsuccessful request just leaves the current price display the same.
new Ajax.Request(go_url, {method: "get",
parameters: "priceTyp="+energyType,
onLoading:function(){ $(elementID).innerHTML=
waiting...";},
onComplete:function(request){
if(request.status == 200) {
$(elementID).innerHTML=
request.responseText;}
}});
The CacheDecider Object
The final piece of the client-side puzzle is the CacheDecider object. Figure 1-2 is a UML diagram depicting this object. I wrote the object in JavaScript using the Prototype library.

Figure 1-2. A Class diagram depicting CacheDecider
Here is the code for the object, which appears in the eevapp.js file.
var CacheDecider=Class.create();
CacheDecider.prototype = {
initialize: function(rng){
//How long do we want to
//keep the associated data, in seconds?
this.secondsToCache=rng;
this.lastFetched=new Date();
},
keepData: function(){
var now = new Date();
//Get the difference in seconds between the current time
//and the lastFetched property value
var secondsDif = parseInt((now.getTime() -
this.lastFetched.getTime()) / 1000);
//If the prior computed value is less than
//the specified number of seconds to keep
//a value cached, then return true
if (secondsDif < this.secondsToCache) { return true;}
//the data in the cache will be refreshed or re-fetched,
//therefore reset the lastFetched value to the current time
this.lastFetched=new Date();
return false;
}
}
The prototype.js file includes an object definition for Class. Calling Class.create() returns an object with an initialize() method. Like a constructor method in other languages, JavaScript will call this method every time the code creates a new instance of an associated object.
Our initialize() method for CacheDecider initializes two properties: secondsToCache, which represents the number of seconds to cache a value before another can be fetched; and lastFetched, a JavaScript Date object representing the last time the cached value, whatever it is, was refreshed.
CacheDecider does not store a reference to a cached value; other objects use a CacheDecider to store and check time limits or ranges.
If this is not clear, check the explanation beneath the "Check the CacheDecider Object First" subheading.
CacheDecider also has a keepData() method. This method determines whether the number of seconds that has elapsed since the lastFetched date exceeds the number of seconds that the cached value is supposed to be kept.
In our application, we hold on to an energy-price value for 24 hours, until it can be refreshed with an HTTP request.
cacher = new CacheDecider(60*60*24);
Resources
Prototype:
http://script.aculo.us/
JavaScript code: http://www.parkerriver.com/ajaxhacks/xml_article_js.zip
Java code: http://www.parkerriver.com/ajaxhacks/xml_article_java.zip
If this number of seconds has not been exceeded, then keepData() returns true. Otherwise, the lastFetched property is reset to the current time (since the cached value can now be refreshed via an HTTP request), and the method returns false.
The user can update the data whenever they want if they reload the entire web page. This action will initialize anew all of the objects that we have been discussing.
The Server Component
This application uses a kind of "cross-domain proxy" design pattern, as discussed at this informative website: http://ajaxpatterns.org/Cross-Domain_Proxy. Even though my application is accessing information from the U.S. EIA web pages, the scraping is initiated by a component that derives from the same domain (eeviewpoint.com) as our application web page.
The server component can be written in your language of choice for scraping web-page information: PHP, Ruby, Python, ASP.NET, Perl, Java. I chose Java, being partial to servlet/JSP programming and the Tomcat container. The Ajax.Request object connects with a JSP, which uses Java utility classes to harvest the energy information from U.S. EIA sites. See the "Resources" box to learn where to download the Java classes I used.
Share your comments in our forum.
(* You must be a member of XML.com to use this feature.)
Comment on this Article
| Titles Only | Titles Only | Newest First |
- browser cache
2006-05-04 15:47:44 jleech [Reply]
Interesting article. But as far as I can tell the cache you've implemented only lasts as long as the user stays on your page.
What I do is set the 'Expires' or 'Max-Age' on the AJAX response from the server, and let the browser cache the data for me. This lets the server decide how long the data is good for, and the cache persists between page visits, browser restarts, etc.
I think you could also do useful things with AJAX caching by setting request headers like 'If-Modified-Since' and reading the 'Expires' and 'Max-Age' response headers using the XMLHttpRequest object.
- browser cache
2006-05-06 05:06:42 bwp [Reply]
That's a great server-side caching strategy. I wonder how consistent across-the-board the browsers are in reading HTTP/1.1, caching related response headers. (As you pointed out, some of these inconsistencies could be dealt with by reading the headers directly from the XMLHttpRequest object.) That would make a really useful follow-up article!
- browser cache
2006-05-07 11:42:20 jleech [Reply]
RE: Server-side caching:
Setting the caching directives on the response works well with caching everywhere, from the server, to intermediate caches, to the browser cache. And it works very well with AJAX using the GET method.
RE: Browser caching inconsistencies:
If I remember correctly, Mozilla and Internet Explorer behave differently on a page reload. One reloads the AJAX-served content, the other still reads it from the cache.
I believe they behave differently also in the case where there are no caching directives in the response header.
RE: Follow-up article:
I would be happy to write one, concerning this or a myriad of other AJAX topics.
- browser cache
2006-08-23 12:54:21 Mark Nottingham [Reply]
Using HTTP caching is much easier and better; as mentioned, it enables caching in the browser, intermediaries, and on the server (if supported).
See:
http://www.mnot.net/blog/2006/05/11/browser_caching
for browser capabilities.
- browser cache
- browser cache
- browser cache
- Nice article - but worthwhile
2006-05-04 06:09:52 dunxd [Reply]
A good article on cacheing in AJAX - but I have to ask. If the data is only updated once a week, why bother having a refresh button on a page? Wouldn't it be a lot more straightforward for the server to get the data in question weekly using cron, or otherwise poll the server with the data on?
- Nice article - but worthwhile
2006-05-04 08:23:29 bwp [Reply]
Yes, that's a very good alternative design, using cron and storing the value on the server weekly.
But what if EIA begins to greatly increase the frequency of displaying the oil price, because the information is so important (my weekly estimate is a purely random observation). Then it makes more and more sense to give the client the ability to request the value in an Ajaxian way (without a complete page reload), particularly if client interactivity is part of the application's requirements in the first place.
Or, what if I find a crude-oil price web service (still looking!) that does hourly updates. I would have to change the servlet to use a web-services API instead of HTML scraping (mostly a change for the better), and then alter the time of client-side caching to about 59 minutes (60*59).
The cron strategy would also have to introduce a tier for storage, such as MySQL or the file system, plus use server-side components to fetch the stored value for the clients.
- Nice article - but worthwhile
