0

I am just beginning to wrap my head around using promises and I could use some guidance on how to get this scenario to work. The code below is from a larger plugin file but I have just included the pieces that I think are relevant.

There is a callback function(callbackBeforeSend) that I will be performing some async geolocation stuff(I've got this working) and I need to hold the ajax call until those functions have completed.

I see in the code that they are using $.Deferred() to handle the ajax response and I am wondering if there is a way to tie the callback function and the initial ajax call into $.Deferred() as well to handle the proper execution order of everything.

So what I would like to happen is

  1. Callback function fires
  2. Async stuff happens in callback and returns lat, lng, address
  3. Ajax fires with lat, lng, address that was returned from callback

Any help would be much appreciated. I still don't understand promises much but I am trying to learn. Thanks!

$.extend(Plugin.prototype, {
    _getData: function (lat, lng, address) {
        var _this = this;
        var d = $.Deferred();

        if (this.settings.callbackBeforeSend) {
            this.settings.callbackBeforeSend.call(this, lat, lng, address);
        }

        $.ajax({
            type         : 'GET',
            url          : this.settings.dataLocation + (this.settings.dataType === 'jsonp' ? (this.settings.dataLocation.match(/\?/) ? '&' : '?') + 'callback=?' : ''),
            // Passing the lat, lng, and address with the AJAX request so they can optionally be used by back-end languages
            data: {
                'origLat' : lat,
                'origLng' : lng,
                'origAddress': address
            },
            dataType     : dataTypeRead,
            jsonpCallback: (this.settings.dataType === 'jsonp' ? this.settings.callbackJsonp : null)
        }).done(function (p) {
            d.resolve(p);

            // Loading remove
            if(_this.settings.loading === true){
                $('.' + _this.settings.formContainer + ' .' + _this.settings.loadingContainer).remove();
            }
        }).fail(d.reject);
        return d.promise();
    }
});
yoxalld
  • 23
  • 7
  • Without changing this code you can't do that. It's not clear to me whether you wrote this code or if it's a third party library that can't be changed. – Felix Kling Aug 19 '15 at 16:29
  • I did not write this code, it is pulled from the plugin file. However I can change the code. I believe I need to tie the callback call and the ajax call into the $.Deferred function. – yoxalld Aug 19 '15 at 16:32
  • Most of what you have looks ok, you just need your `callbackBeforeSend` to return a promise and do your ajax call in `callbackBeforeSend.then(...)` – Dave Aug 19 '15 at 16:37
  • Start by [promisifying only the callback function](http://stackoverflow.com/q/22519784/1048572), and nothing else. Then use that function which returns a promise and combine it with `$.ajax`. – Bergi Aug 19 '15 at 16:39

3 Answers3

0

I really really don't like jQuery promises but if you must...

function callBackBeforeSend(){
   var def = $.Deferred();
   //Async operation here:
   async(function(lat, lng, address) {
       //pass resolve an object with all information
       //the async callback function might not use 3 parameters, but an 
       //object containing all
       def.resolve({ lat : lat, lng : lng, address : address});
       //async function callback
    });
   return def.promise();
}

Then you can use deferred.then() to get the resolved data.

var prom = callbackBeforeSend();
prom.then(function(obj){
    //lat, lng, address within obj
    //use on ajax call below.
    $.ajax();
});

You can then use then to chain the methods I'd also check out which jQuery version you are using. Behaviour before jQuery 1.8 might be different.

Here's using ES6 promises:

function callbackBeforeSend(){
    return new Promise(function(resolve) {
         async(function(lat, lng, address){
             resolve({lat : lat, lng : lng, address : address});
         });
    });
}

Then it's the same thing:

var prom = callbackBeforeSend(); //firstRequest
prom.then(function(obj){
    //Callback finished
    //obj holds lat, lng, address
    //Do ajax request:
    $.ajax();
});
MinusFour
  • 13,913
  • 3
  • 30
  • 39
0
  1. Callback function fires
  2. Async stuff happens in callback and returns lat, lng, address
  3. Ajax fires with lat, lng, address that was returned from callback
$.when(callback())
.then(function(data) {
  // `data`: `lat` , `lng`, `address` ...
  var settings = {url:url, data:{...}, type:"GET"};
  return $.ajax(settings)
}, function(jqxhr, textStatus, errorThrown) {
  console.log(errorThrown);
  return errorThrown
})
.then(function(data) {
  // do stuff when `callback` , `$.ajax()` completes
}, function(err) {
  console.log(err);
});
guest271314
  • 1
  • 15
  • 104
  • 177
0

Probably the most important things to realise are :

  • that the requirement to (optionally) call the asynchronous callbackBeforeSend() before performing the ajax, requires that a Promise is either returned by callbackBeforeSend() or created.
  • that $.ajax() returns a jqXHR object, which is effectively a Promise, therefore $.ajax() does not require you to create/resolve your own Deferred.

Try this :

$.extend(Plugin.prototype, {
    _getData: function (lat, lng, address) {
        var settings = this.settings;
        var promise = settings.callbackBeforeSend ? $.when(settings.callbackBeforeSend.call(this, lat, lng, address)) : $.when();
        return promise.then(function() {
            return $.ajax({
                type: 'GET',
                url: settings.dataLocation + (settings.dataType === 'jsonp' ? (settings.dataLocation.match(/\?/) ? '&' : '?') + 'callback=?' : ''),
                data: { 'origLat':lat, 'origLng':lng, 'origAddress':address },
                dataType: dataTypeRead,
                jsonpCallback: (settings.dataType === 'jsonp' ? settings.callbackJsonp : null)
            });
        }).then(null, function(jqXHR, textStatus, errorThrown) {
            return errorThrown;
        });
    }
});

If settings.callbackBeforeSend() already returns a promise, the $.when(...) wrapper can be dispensed with.

Your "loading" indication should really (by convention) be appended and later removed in the same block of code - either inside _getData() or in the calling function.

For example, the calling function might call ._getData() and act on its outcome as follows :

var $spinner = $("#myLoadingElement").appendTo('.wherever');//pseudocode
foo._getData(lat, lng, address).then(function(data) {
    //do whatever on success
}, function(error) {
    //do whatever on error
}).always(function() {
    $spinner.remove();//remove the spinner regardless of the outcome.
});
Roamer-1888
  • 19,138
  • 5
  • 33
  • 44