Zach’s ugly mug (his face)

Zach Leatherman

Deferreds and a Better Geolocation API

07 Nov 2011 Read in about 4 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:

getCurrentPositionDeferred({
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.

3 Comments

➡ Load Disqus to Leave a Comment ⬅

This comment is only (barely) tangentially related to your post's main point (which is very helpful to me).

In my experience, it may be a bit dangerous to rely on 'navigator.geolocation.getCurrentPosition' for accurate locations from devices such as mobile phones. The code is only getting the first location the hardware is able to return. Sometimes it is very accurate. Other times, the first given location is well off target, such as the nearest cell tower. I've found it may be a better user experience to use 'navigator.geolocation.watchPosition()' and let the user decide when to accept the given location. Of course this all depends on what you are trying to do.

I hope this helps someone as much as reading this post about deferred helps me.

There's a proposal for adding deferreds to ES.Next fwiw http://wiki.ecmascript.org/...

Zach Leatherman

30 Nov 2011 at 07:54PM

@Tom: There is a "enableHighAccuracy" option, did you try that? http://dev.w3.org/geo/api/s...

@Paul: Ah, cool! It would be very useful to have Deferred objects independent of jQuery.