37

I have a function that gets the location through navigator.geolocation:

var getLocation = function( callback ){

    navigator.geolocation.getCurrentPosition( callback || function( position ){

        // Stuff with geolocation

    });

};

I would like to make it so that I could chain this function using jQuerys' Deffered object but I have still not managed to grasp the concept and usage of Deffered.

I'm looking for something similar to this Pseudo Code:

getLocation().then(function(){
    drawMarkerOnMap();
});

Is this syntax even possible without flipping over backwards and drowning in code?

hitautodestruct
  • 20,081
  • 13
  • 69
  • 93

3 Answers3

67

You have to instantiate a new deferred object and return it (or its promise) from the function. Call its .resolve method once you get the response:

var getLocation = function() {
    var deferred = new $.Deferred();

    navigator.geolocation.getCurrentPosition(function( position ){
        // Stuff with geolocation
        deferred.resolve(position);
    });

    // return promise so that outside code cannot reject/resolve the deferred
    return deferred.promise();
};

Usage:

getLocation().then(drawMarkerOnMap);

Reference: jQuery.Deferred


Addendum:

I would advise against using both approaches, deferred objects and passing callbacks to the function, to keep the interface simple. But if you have to stay backwards compatible, you can simply register the passed callback at the deferred object:

var getLocation = function(callback) {
    var deferred = new $.Deferred();

    if ($.isFunction(callback)) {
        deferred.then(callback);
    }

    navigator.geolocation.getCurrentPosition(function( position ){
        // Stuff with geolocation
        deferred.resolve(position);
    });

    // return promise so that outside code cannot reject/resolve the deferred
    return deferred.promise();
};
Beetroot-Beetroot
  • 18,022
  • 3
  • 37
  • 44
Felix Kling
  • 795,719
  • 175
  • 1,089
  • 1,143
  • Thank you for the response and fantastic answer! When you say "..advice against using both approaches, deferred objects and passing callbacks.." how else would you support an asynchronous request such as `getCurrentPosition`? – hitautodestruct Jan 17 '13 at 11:19
  • 2
    @hitautodestruct: No, what I meant is having `getLocation` accept a callback *and* return a deferred object. I.e. there would be two ways, either `foo(bar)` or `foo().then(bar)`. There should only be one way to call that function. – Felix Kling Jan 17 '13 at 11:28
  • Look at [this answer](https://stackoverflow.com/a/52800429/414385) if you don't use/need jquery – hitautodestruct Mar 20 '19 at 08:20
2

Even though the above example did help me I had to do a bit more reading to wrap my head around the concept.

Below is example based on my code that contains comments to assist me when I come back to it and hopefully anyone reading this Stackoverflow question:

/* promise based getFilter to accommodate getting surrounding suburbs */
oSearchResult.fPromiseOfFilterSetting = function fPromiseOfFilterSetting(sId) {
    var self = this;
    self.oPromiseCache = self.oPromiseCache || {}; // creates a persistent cache 
                                                   // across function calls
    var oDeferred = $.Deferred(); // `new` keyword is optional
    var oPromise = oDeferred.promise();

    // leverage the cache (it's ok if promise is still pending), you can key
    if (self.oPromiseCache[sId] !== undefined) {
        return self.oPromiseCache[sId];
    }
    else {
        self.oPromiseCache[sId] = oPromise;
    }

    // do our asynchronous action below which at some point calls
    // defered.resolve(...) and hence complete our promise
    $.cmsRestProxy.doAjaxServiceRequest('ocms_searchProperties_Extension', {
        action : 'getSurroundingSuburbs',
        sSuburbIds : 'a0RO0000003BwWeMAK'
    }, function(result, json) {
        console.log("doAjaxServiceRequest(
                       'ocms_searchProperties_Extension')", json);
        oDeferred.resolve(json); // `json` is our result and `.resolve(json)` 
                                 // passes the value as first argument to 
                                 // the `oPromise.done`, `oPromise.fail` 
                                 // and `oPromise.always` callback functions
    })

    // We can now return the promise or attach optional `oPromise.done`,
    // `oPromise.fail`, and `oPromise.always` callbacks which will execute first
    // in the chain.
    //
    // Note that `oPromise.then(doneCallback, failCallback, alwaysCallback)`
    // is short form for the below
    oPromise.done(function(value) { // returned by promise.resolve(...); call
        console.log('will run if this Promise is resolved.', value);
    })
    oPromise.fail(function(value) {
        console.log("will run if this Promise is rejected.", value);
    });
    oPromise.always(function(value) {
        console.log("this will run either way.", value);
    });

    // return a promise instead of deferred object so that
    // outside code cannot reject/resolve it
    return oPromise;
}

// then to use one would do
oSearchResult.fPromiseOfFilterSetting().done(function(value) {alert(value)});

// or using $.when chaining
$.when(
    oSearchResult.fPromiseOfFilterSetting()
)
.done(
      function fDoneCallback(arg1, arg2, argN) {
          console.debug(arguments) // `arguments` is an array of all args collected
      }
);
Daniel Sokolowski
  • 11,982
  • 4
  • 69
  • 55
  • 1
    Can you please use the `navigator.geolocation` as asked in the question? Otherwise, it might be a nicely commented piece of code, but not an **answer**. – Bergi Jul 15 '14 at 03:23
  • No, `oPromise.then(…)` is *not* equivalent to `oPromise.done().fail().always()`. The last callback is for [progress](http://api.jquery.com/deferred.progress/) events! – Bergi Jul 15 '14 at 03:25
  • And it's not even equivalent to `oPromise.done().fail().progress()`. The [`then` method](http://api.jquery.com/deferred.then) is the basis for the [monadic functionality](http://stackoverflow.com/q/22539815/1048572) of promises – Bergi Jul 15 '14 at 03:27
  • Notice that you shouldn't use `$.when` when you know that the argument is a promise. – Bergi Jul 15 '14 at 03:28
1

I know it says jQuery in the title but when I asked this question promises were new to the web and jQuery was the de facto library. Here's a more modern answer without jQuery.

Use a native Promise

All modern browsers (except for IE11 and below; use a polyfill if needed) enable you to use a native Promise construct.

let getLocation = () => {

  return new Promise( ( resolve, reject ) => {

    try {
      navigator.geolocation.getCurrentPosition( position => {
        resolve( position )
      })
    } catch ( err ) {
      reject( err )
    }

  })

};

Usage:

let runGetLocation = () => { getLocation().then( position => console.log( position ) ) }

You can also use ES2016 async/await instead of .then():

let runGetLocation = async () => {

  try {
    let position = await getLocation()
    console.log( position )
  } catch ( err ) { console.log( err ) }

}
hitautodestruct
  • 20,081
  • 13
  • 69
  • 93