Adventures in JavaScript Development

Making Sense of dojo.when: A Simple XHR Caching Example

| Comments

Right before Dojo 1.5 came out, the Sitepen blog had a great post about the improvements 1.5 would bring for dojo.Deferred. The part that really caught my eye was dojo.when, a method that lets you pass a value to a function whether that value is available now or as the result of some asynchronous operation. Either way, you get a “promise” that when the value is available, the function you provided will run.

This is one of those things that was super-neat when I read about it, but it took me a while to incorporate it into my code — it’s only in the last couple of weeks that I’ve had that wonderful moment when I’ve said “oh, I could totally use dojo.when for that!” Moments like these make me very happy.

It’s pretty common that an application makes an Ajax request for some data, and then caches that data so the request won’t have to happen again; the pattern might look something like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var myCache = {};

function getSomeStuff(stuffId) {
  if (myCache[stuffId]) {
    handleResponse(myCache[stuffId]);
    return;
  }

  dojo.xhrGet({
    url : 'foo.php',
    content : { id : stuffId },
    load : function(response) {
      myCache[stuffId] = response;
      handleResponse(response);
    }
  });
}

Here we have a function that takes an ID; the function looks in the cache to see if there’s a value stored for the ID, and if so, it passes the stored value to a handleResponse function. If not, it does an XHR to get the data; when the XHR succeeds, it stores the data in the cache and, again, passes the value to the handleResponse function.

There’s nothing strictly wrong with this, but I discovered that some neat abstraction opportunities became more clear when I switched to using dojo.when instead:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var myCache = {};

function getSomeStuff(stuffId) {
  dojo.when(
    myCache[stuffId] || dojo.xhrGet({
      url : 'foo.php',
      content : { id : stuffId },
      load : function(response) {
        myCache[stuffId] = response;
      }
    }),
    handleResponse
  );
}

Now we’re telling our getSomeStuff function to look for a cached value; if it finds one, dojo.when will immediately pass that value to the handleResponse function. If it doesn’t find one, it will run the XHR, and dojo.when will magically pass the XHR’s response to the handleResponse function instead. This is hot.

This works because dojo.xhrGet returns a “promise” object with a then method. dojo.when looks to see whether it got a promise object as its first argument; if so, it uses the then method of the promise object to attach the callback provided as the second argument to dojo.when. If not, it simply calls the callback immediately on the first argument. The real magic is actually in dojo.Deferred, not in dojo.when itself. Since all of Dojo’s XHR methods return a dojo.Deferred promise, dojo.when will “just work.”

I found that I was going through my application and ripping out instances of the old code, replacing it with the new. And then I had that “oh sh*t I’m copying and pasting, aren’t I …” moment, and saw my way to an abstraction.

In my application, I was actually caching the responses using the URL from which I’d requested them, which works out to be a perfectly unique ID for the data. (This particular part may or may not work in your application.) My abstraction was an essentially drop-in replacement for dojo.xhrGet calls:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var cache = {};

function cacheableXhrGet(settings) {
  var url = settings.url,
      req = cache[url] ||
            dojo.xhrGet(dojo.mixin({
              // override the load handler
              load : function(resp) {
                cache[url] = resp;
              }
            }, settings));

  dojo.when(req, settings.load);
  return req;
}

I can pass a settings object to cacheableXhrGet that looks exactly like the object I’d pass to dojo.xhrGet, but replace the load function before actually passing it to dojo.xhrGet. But before the XHR even has a chance to get set up, I check my cache for a stored response; if I find one, I store it in the req variable, but otherwise I store the XHR there.

In either case, the function defined at settings.load gets the proper response value via dojo.when. For bonus points, I then return either the cached value or the XHR — which means other code can use the return value of cacheableXhrGet for its own dojo.when. How neat is that?

Conclusion

Promises and deferred’s are a really pleasant tool to have in your JavaScript arsenal once you get the hang of them, and dojo.when seems like a great place to start understanding them.

Out of the box, Dojo makes use of deferreds for all of its XHR functionality, meaning that you can pass around the return value of any Dojo XHR method and do fun things you can’t do with jQuery’s $.ajax, like add more callbacks to a request after you’ve set it up.

I’ve just recently started realizing when I could incorporate dojo.Deferred functionality into my own code — again, now that I’ve got the hang of it, I’m pretty sure it’s going to dramatically change how I write asynchronous code.

Disclaimer: This post contains sample code for illustration purposes. In reality it’s all namespaced and these naked functions are actually methods in classes and stuff, and the real code doesn’t even look much like the code you see here. I’ve also completely ignored questions of when to clear or invalidate the cache. You’ve been warned.

Comments