mtelligent

View Original

Deferreds, Geolocation and Reverse Geocoding with Google Maps API

One of the most useful but little used aspects of jQuery is the Deferred object. It's jquery's implementation of promises and makes it easier and cleaner to expose functionality that is asyncronous. 

I've been working quite a bit with Geocoding and the Google Maps API and usage of deferred to expose this functionality has been very useful.

The basic idea is that instead of relying on callbacks to alter the state of global objects, you can pass a deferred object which will notify the caller when the result is ready to be processed.

Take the standard navigator.geolocation.getCurrentPositionMethod. This is a built it method that will retreive the user's current location if they allow it. Normally you pass to callback functions to this method, one for success and one for failure. By wrapping this call with a method that returns a deferred object, the resultant code is much cleaner for handling the result. Take this implementation as an example:

var getLatLong = function() {
        var def = $.Deferred();

        if (navigator.geolocation){
            navigator.geolocation.getCurrentPosition(
                function (position){
                    def.resolve(position);
                }, 
                function (error) {
                var errors = { 
                    1'Permission denied',
                    2'Position unavailable',
                    3'Request timeout'
                  };
                    def.reject("Error: " + errors[error.code]);
                },
                {timeout:10000});
        }
        else
        {
            def.reject("geolocation not supported");
        }

        return def;
      };

Instead of just firing the call and leaving the callbacks to alter state, we can referred the deferred object and the caller can respond to the result on success, failure or both.

Take this method "getCurrentAddress" which relies on our first method and then calls google's geocoding api to translate the user's current latitude and logitude to an address.

var getCurrentAddress = function (){
        var def = $.Deferred();
        getLatLong().done(function(position){
          //we have current Lat and long in settings object.
          var latlng = new google.maps.LatLng(position.coords.latitude,position.coords.longitude);
          
geocoder.geocode({'latLng': latlng}, function(results, status) {

          if (status == google.maps.GeocoderStatus.OK) {
            if (results[0]) {
              def.resolve(results[0].formatted_address);
            }
          } else {
              def.reject("Geocode Failed: " + status);
          }});

        });

        return def;
      }

It chains the result of the call to our first method to the done function, which will automatically be invoked when the first call completes succesfully and the position object is automtically passed as a parameter. We then use that to call Google's geocoding api to get an address back. The whole thing itself also leverages deferred, so we can easily call it and get back the address when it's done. Here's the call on the page which gets the result and updates a div on the page with the user's current address:

<script>
getCurrentAddress().done(function(address){
     $("#address").text(address);
});
</script>

We also could have handled failures, as well as implemented code that executes regardless of the outcome of the deferred, so this approach gives you a very robust approach to dealing with asyncronous dependencies.