zachleat’s Ugly Mug

Zach Leatherman

Deferreds and a Better Geolocation API

07 Nov 2011 Read in about 6 minutes

Warning, this article is intended for Deferred unbelievers to convince them that Deferred objects are both easy and useful. If you’re already a Deferred object expert, you might want to skip this one.

Earlier this year I was given the opportunity to attend the jQuery Conference in San Francisco. I was delighted to go, able to finally meet some of the JavaScript greats I’d been stalking following online for years.

Looking back on the conference, the one presentation that had the biggest impact on the way that I code had to have been Dan Heberden‘s “Deferreds, Putting Laziness to Work.” (I would be remiss if I didn’t also mention inspiration from a recent presentation by Eli Perelman at the Omaha jQuery Meetup.)

At first, Deferred objects sound scary. I can assure you that they’re actually incredibly easy and incredibly useful. Today we’ll go through the simple task of reworking the Geolocation API to use jQuery Deferred objects.

Here is the standard Geolocation API to retrieve the user’s current position:

navigator.geolocation.getCurrentPosition(function(position) {
// success
}, function(error) {
// failure
}, {
// options
enableHighAccuracy: true

When the above API is called, a prompt is shown to the user asking if they want to divulge their location information to the domain of the currently active web site. Typically this prompt is a non-blocking asynchronous operation (although not explicitly defined in the specification).

Let’s go ahead and change it to use a jQuery Deferred object:

function getCurrentPositionDeferred(options) {
var deferred = $.Deferred();
navigator.geolocation.getCurrentPosition(deferred.resolve, deferred.reject, options);
return deferred.promise();

Notice that the success callback is replaced by the deferred object’s resolve method and the error callback is replaced by the reject method. All of our function arguments are removed from the API. We’re left with one simple options argument.

This allows us to do things like:

enableHighAccuracy: true
}).done(function() {
// success
}).fail(function() {
// failure
}).always(function() {
// executes no matter what happens.
// I've used this to hide loading messages.
// You can add an arbitrary number of
// callbacks using done, fail, or always.

We could also use $.when to run code upon completion of two arbitrary and contrived operations like a Geolocation call and an Ajax request. Awesome.

To coordinate between multiple Deferred objects, use $.when:

$.when(getCurrentPositionDeferred(), $.ajax("/someUrl")).done(function() {
// both the ajax call and the geolocation call have finished successfully.

I wonder what other browser native APIs could be better served by using Deferred objects instead of function arguments.