[Coding] jQuery and Deferred Objects (a problem solving work in progress)

Posted by Khatharsis on March 22, 2013

I had a job interview yesterday in which I demoed one of my works in progress – a Here/There app. I haven’t added it to my portfolio yet because it’s far from finished in terms of customization and display, but the basic premise is there. The app is written in HTML 5, uses canvas, a couple of AJAX calls to retrieve weather information and time, and is currently made for the iPhone 5, hence the odd resolution. A future goal is to properly resize the canvas for a wider variety of mobile devices.

One of the problems I ran into was it’s nitpickiness at having two AJAX calls having callback functions that updated the canvas. At the time I was working on it (last week), I wasn’t running Firebug so I was doing a lot of manual tinkering and generally getting my hands dirty to see what code orientations would cause which results. I eventually got the app working by essentially chaining or nesting the AJAX calls, saw that it worked, and set it down. Fast forward to yesterday, I am explaining the problem I ran into and am advised to look into jQuery’s Deferred Object. I’ve only spent an afternoon poking at it so my understanding isn’t fully there, but I feel the resulting code that I have is much messier and less elegant than my original solution. Which is better?

In the usual case of irony that accompanies telling fellow programmers about coding issues I run into, I either figure out what was wrong and it’s a silly reason (like a text file that didn’t copy all of it’s contents when it was moved from one machine to another) or it magically works. In this case, it was the latter. I went back to my code and unnested my Ajax calls and lo and behold, my app functions as it should. However, I got the sense that, while that particular structure was my original intent with the code, my previous solution still felt a little more elegant.

Here’s some code. Note, this is not the exact code that I use, but just wanted to make it clean for demonstration purposes:

// This function gets called first, sort of like a wrapper
function setUpCanvas() {
	var ctx = canvas.getContext('2d'); //this is not how to actually get the canvas object
	var backgroundImage = new Image();
	backgroundImage.onload = function() {
		ctx.drawImage(backgroundImage, 0, 0);
		getWeather();
	};
	backgroundImage.src= 'someImage.jpg';
}

// First AJAX call
function getWeather(ctx) {
	var weatherObj = {};
	var yqlQuery = 'some query string to get weather data';
	$.getJSON(yqlQuery, function(data) {
		weatherObj.here = data.weather.here;
		weatherObj.there = data.weather.there;
		getTime(ctx, weatherObj);
	});
}

// Second AJAX call
function getTime(ctx, weatherObj) {
	var timeObj = {};
	var phpScript = 'customScriptPage.php';
	$.getJSON(phpScript, function(data) {
		timeObj.here = data.time.here;
		timeObj.there = data.time.there;
		printObjects(ctx, weatherObj, timeObj);
	});
}

// Last function prints meaningful data to the canvas
function printObjects(ctx, weatherObj, timeObj) {
	// here block
	// ..
	// ..

	// there block
	// ..
	// ..
}

What I find elegant about this solution is the separation of tasks. A weather object is created independently of the time object, then both objects can be passed into the printObjects() function which handles all of the necessary font sizing and color tasks. The only problem is the sequential ordering and the need to pass objects to each nested function. Imagine if I had 5 nested calls before calling printObjects – that’s not very elegant. Ideally, I should be able to send out the $.getJSON requests one right after the other and start constructing my objects without having to wait for the other function to return. When both functions are done and have given me the constructed objects I desire, then I can call printObjects().

jQuery’s deferred object is ultimately what I’m looking for, but in my hasty search for a quick understanding of how it works so I can implement it did not work out so well. See an example of the reworked 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 = {};
	var yqlQuery = 'some query string to get weather data';
	var phpScript = 'customScriptPage.php';

	backgroundImage.onload = function() {
		ctx.drawImage(backgroundImage, 0, 0);
		$.when($.getJSON(yqlQuery), $.getJSON(phpScript))
		.then(function(weatherData, timeData) {
			weatherObj.here = weatherData.weather.here;
			weatherObj.there = weatherData.weather.there;

			timeObj.here = timeData.time.here;
			timeObj.there = timeData.time.there;

			printObjects(ctx, weatherObj, timeObj);
		});
	};
	backgroundImage.src= 'someImage.jpg';
}

function printObjects(ctx, weatherObj, timeObj) {
	// here block
	// ..
	// ..

	// there block
	// ..
	// ..
}

I should caution that this is a work in progress. It works, which is important, but I don’t like how it works. Or looks. It sends out both AJAX calls, waits for both of them to return ($.when), then sets up the objects for printing ($.then). This isn’t quite the solution I had in mind. Yes, the code is significantly shorter, but I feel it’s also considerably messier. Ideally, I’d want to use $.when but pass in the functions getWeather() and getTime() which will be modified to return their respective constructed objects. ($.when takes deferred objects like $.ajax or $.getJSON as arguments or plain JS objects) Then all I would need to do in $.then is call printObjects(), passing in the canvas and the returned weather and time objects as arguments. I’m hoping there is a solution that will allow me to do that, but I haven’t found it yet.

I have a sneaking suspicion that what I really want is an event listener that will sit around and not fire a callback until it hears back from both getWeather() and getTime(). My understanding with asynchronous calls that return an object in their callbacks is that object won’t get assigned to a variable (e.g., var weatherObj = getWeather();). The code isn’t marked to make sure the assignment happens, it (the variable) just gets set to undefined and moves on its merry way. I would have to fire off a custom event containing the object in the callback for the event listener to pick up and handle instead.