Search code examples
jqueryajaxjquery-deferred

jQuery Deferred then() called before resolved


I'm new to jQuery Deferred and don't understand why then is called before the success function completes. I have two simultaneous ajax requests and would like to trigger a UI radio button click to iterate over some data returned from one of the ajax calls, which is taking 20 seconds to respond. However, when the radio button click is triggered inside then(), the data to iterate over is null. if I click a different radio button to filter the data, and then click the triggered radio button, the data is not null because enough time has elapsed to successfully load the object.

The object gets loaded in success of the first (20 sec) ajax call, and the click is triggered in then(), when both of these calls are resolved. Why would then() trigger the click if success hasen't finished?

    firstAjaxFunc: function(args){          
        return $.ajax({
            type: "POST",               
            url: REQUEST_URL + "/first-func",
            contentType: "application/json",
            data: $.toJSON({
                propOne: propOne,
                propTwo: propTwo
            }),
            dataType: "json class.models",
            context: args.context,
            success: args.success,
            error: showResponseError
        });
    },

    secondAjaxFunc: function(args){
        return $.ajax({
            type: "POST",               
            url: REQUEST_URL + "/second-func",
            contentType: "application/json",
            data: $.toJSON({
                propOne: propOne,
                propTwo: propTwo
            }),
            dataType: "json class.models",
            context: args.context,
            success: args.success,
            error: showResponseError
        });
    },

        $('#load-data').append('<p class="spinner" align="center"><img src="../images/loading_small.gif"/></p>');
        this.firstAjaxFunc({
            params: {
                propOne: propOne,
                propTwo: propTwo
            },
            success: function(data){
                this.options.stuff = data;
            },
            context: this
        });
        this.secondAjaxFunc({
            params: {
                propOne: propOne,
                propTwo: propTwo
            },
            success: function(data){
                this.options.moreStuff = data;
            },
            context: this
        });
        $.when(firstAjaxFunc, secondAjaxFunc).then(this.find(".filter-all").click());

    ".filter-all click": function(radio, event){ 
            this.showAllData();
    },

    showAllData: function(){
        for(var i = 0; i < this.options.stuff.length; i++){ // this.options.stuff is null
            $.merge(stuffArr, this.options.stuff[i].getMyStuff());
        }
    },

Solution

  • I see two main issues.

    $.when() accepts promises as arguments, NOT callback functions. As such, you need to actually call your functions that you pass to it like this:

     $.when(this.firstAjaxFunc(...), this.secondAjaxFunc(...)).then(...);
    

    AND, a .then() handler MUST be a callback function so you have to turn your .then() handler into a callback function that can be called later. As you have it now, it is getting executed immediately and its result is passed to .then() which explains why the .click() is executed immediately.


    In addition, it is best NOT to mix jQuery success handlers with promise handlers. Pick one of the other and use them consistently. In this case, since you want some promise functionality, you should probably just use promises because the calling order and sequences of promise handlers is strictly described in the promise specification so there can be no ambiguity about calling order.


    You are only showing pieces of code so it's hard to get the full context, but I think you want something like this:

        var p1 = this.firstAjaxFunc({
            params: {
                propOne: propOne,
                propTwo: propTwo
            },
            success: function(data){
                this.options.stuff = data;
            },
            context: this
        });
        var p2 = this.secondAjaxFunc({
            params: {
                propOne: propOne,
                propTwo: propTwo
            },
            success: function(data){
                this.options.moreStuff = data;
            },
            context: this
        });
        $.when(p1, p2).then(function() {
            this.find(".filter-all").click();
        }, showResponseError);
    

    Or, converting everything to promises:

        var self = this;
        var p1 = this.firstAjaxFunc({
            params: {
                propOne: propOne,
                propTwo: propTwo
            }
        });
        var p2 = this.secondAjaxFunc({
            params: {
                propOne: propOne,
                propTwo: propTwo
            }
        });
        $.when(p1, p2).then(function(data1, data2) {
            // process the results
            self.options.stuff = data1[0];
            self.options.moreStuff = data2[0];
    
            // carry out action now that both are done
            this.find(".filter-all").click();
        }, showResponseError);