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
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
There’s nothing strictly wrong with this, but I discovered that some neat abstraction opportunities became more clear when I switched to using
1 2 3 4 5 6 7 8 9 10 11 12 13 14
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
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.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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
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?
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.