0

I've got a problem where I've got three Ajax calls I need to make to retrieve some strings. If any of them fail, I need to use a default string instead. I want my page to wait until all the strings have been resolved (or rejected) before moving on.

What I have so far looks something like this:

var myObj = {
    initialize: function(){
        // Start fetching this stuff right away
        var that = this;
        this.promise1 = $.ajax(url: 'url1').then(function(result){
            that.string1 = result
        ).fail(function(){
            that.string1 = 'string1Default';
        });
        this.promise2 = $.ajax(url: 'url2').then(function(result){
            that.string2 = result
        ).fail(function(){
            that.string2 = 'string2Default';
        });
        this.promise3 = $.ajax(url: 'url3').then(function(result){
            that.string3 = result
        ).fail(function(){
            that.string3 = 'string3Default';
        });
    }

    getStrings: function(){
        return {
            string1: this.string1, 
            string2: this.string2, 
            string3: this.string3
        };
    }

    doThingWithStrings: function(callback){
        var that = this;
        return $.when(this.promise1, this.promise2, this.promise3)
            .always(function(){callback(that.getStrings())})
    }
}

myObj.initialize(); // start fetching
// ... do some other stuff
myObj.doThingWithStrings(loadThePageWithStrings);

There are two problems with this. One is that it just feels harder than it should be.

The second more important problem is that $.when executes .done() when all have been resolved, but .fail() as soon as any have been rejected. What I really need is something that executes after all promises have are no longer pending, whether they resolved successfully or were rejected. $.when seems to be almost, but not quite, what I want. I don't want to show the page until I've retrieved (or failed to retrieve) each of the three strings. How can I do this?

Sterno
  • 1,638
  • 2
  • 17
  • 28
  • After more research (I promise, I looked before I wrote this!) I think maybe this is a duplicate of [this](http://stackoverflow.com/questions/6538470/jquery-deferred-waiting-for-multiple-ajax-requests-to-finish/13627555#13627555) – Sterno Aug 04 '15 at 18:13

1 Answers1

0

The answer you found is not a complete answer to this question. It gets you only part way there.

The most important thing that's missing is the correct injection of defaults, which is central to this question. You need to know how to :

  • catch ajax errors,
  • inject the defaults,
  • convert failure into success.

These three things can be achieved very simply by forming three $.ajax(...).then(...) chains, each with an error handler that returns a jQuery promise resolved with the required default string :

$.ajax(...).then(null, function() {
    return $.when('default');
});

Also .getStrings() must return a promise (the cached aggregate promise). Where asynchronously derived data is concerned, the only reliable approach is to return a promise (an abstraction of the data), not the data itself.

To make the code neater, you can do several other things :

  • conditionally call .initialize() from .getStrings() in case it has not already been called.
  • aggregate the promises in .initialize() and cache the resulting promise, as opposed to caching the three jqXHR promises individually.
  • purge .doThingWithStrings() in favour of calling .getStrings() directly. There's no advantage in having an intermediate method that accepts a callback when the object returned by .getStrings() is a promise, whose .then() method will itself accept success/error callbacks.

Here's myObj :

var myObj = {
    initialize: function() {
        var promise1 = $.ajax({url: 'url1'}).then(null, function() {
            return $.when('string1Default');//inject default on ajax failure
        });
        var promise2 = $.ajax({url: 'url2'}).then(null, function() {
            return $.when('string2Default');//inject default on ajax failure
        });
        var promise3 = $.ajax({url: 'url3'}).then(null, function() {
            return $.when('string3Default');//inject default on ajax failure
        });
        this.promise = $.when(promise1, promise2, promise3).then(function (str1, str2, str3) {
            return {
                'string1': str1, 
                'string2': str2, 
                'string3': str3
            };
        }); 
    },
    getStrings: function() {
        if(!this.promise) {
            this.initialize();//safety, in case initialize() has not been called
        }
        return this.promise;
    }
}

And here's how to call myObj.getStrings() :

myObj.getStrings().then(function(stringsObject) {
    // do stuff with stringsObject here.
    console.dir(stringsObject);
});
Community
  • 1
  • 1
Roamer-1888
  • 19,138
  • 5
  • 33
  • 44