[Coding] jQuery and Deferred Objects (a potential solution)
Posted by Khatharsis on March 23, 2013
The naive solution I wrote for using jQuery’s deferred objects was bugging me so much yesterday that I was hacking away at it last night. A revelation didn’t occur to me until almost 11pm (nearly 8 hours after I started). While my code looks more like how I want it, the concept of using a reference to set the properties of my weather and time objects still makes me uneasy. Perhaps it is because my first “real” programming language was Java in which everything is passed by value.
I spent a little bit of time looking into custom events, but I kept being drawn back to deferred objects. It turns out that adjusting my approach just slightly would give me the effect I wanted, that is, still being able to separate the construction of the weather and time objects, and only when both objects have been constructed, print to the canvas. Some code below:
function setUpCanvas() {
var ctx = canvas.getContext('2d'); //this is not how to actually get the canvas object
var backgroundImage = new Image();
var weatherObj = {};
var timeObj = {};
backgroundImage.onload = function() {
ctx.drawImage(backgroundImage, 0, 0);
$.when(getWeather(weatherObj), getTime(timeObj))
.then(function() {
printObjects(ctx, weatherObj, timeObj);
});
};
backgroundImage.src= 'someImage.jpg';
}
// First AJAX call
function getWeather(obj) {
var deferred = $.Deferred();
var yqlQuery = 'some query string to get weather data';
$.getJSON(yqlQuery, function(data) {
obj.here = data.weather.here;
obj.there = data.weather.there;
deferred.resolve();
});
return deferred.promise();
}
// Second AJAX call
function getTime(obj) {
var deferred = $.Deferred();
var phpScript = 'customScriptPage.php';
$.getJSON(phpScript, function(data) {
obj.here = data.time.here;
obj.there = data.time.there;
deferred.resolve();
});
return deferred.promise();
}
function printObjects(ctx, weatherObj, timeObj) {
// here block
// ..
// ..
// there block
// ..
// ..
}
Instead of returning a weatherObj or timeObj, I now return a deferred promise, which allows $.when() to take these methods as arguments. From my extremely, strictly surface-level understanding, $.when() calls each of the functions in its parameters. Function execution performs as usual and an AJAX call is sent out, but may return after the deferred promise has already returned to $.when(). When the AJAX call is sent back, any handling or manipulation can be done in its callback. The important line is deferred.resolve(). This tells $.when() that the AJAX call has returned and anything that needs to be done with the data it returned has completed.
All good, right?
Well, in OOP, I’m more accustomed to passing around objects and returning objects for other functions to use. It’s sort of ingrained and I can’t help but think that this approach rubs me just the wrong way. (Also, the function names getWeather() and getTime may be better named as setWeatherObjectFields() and setTimeObjectFields() in this new context.) However, JS’s pass-by-reference instead of pass-by-value means I can do little tricks like passing in a reference (obj) to the desired object that exists in the parent function. Then, I just have to twiddle my thumbs until both deferreds resolve, letting me know that both objects have been set as I like them, and can then pass them (again as references) confidently to the print function. The other plus of this is saving the space and general computation to needed to construct and pass objects and copies of objects around.
Another programming-related concept is DRY – don’t repeat yourself. There’s a clear template in both of my object construction methods, but due to the simple fact that the JSON calls require different queries and different ways of retrieving the values–including, which I did not explicitly show here, that each object has a different set field names and not just .here and .there; I kept them simple to keep with the theme–I’d be going against KISS – keep it simple stupid – trying to alleviate DRY. Compartmentalizing the code into separate functions make sense for me and I find it elegant, but I fear that more experienced programmers may disagree and have other methods, which I may or may not agree with.
Nonetheless, I felt quite happy last night figuring out this particular puzzle and now have a better grasp of deferred objects from which I can draw on in the future.