G--
G--

Reputation: 507

Return promise from recursive Jquery ajax

I have a Jquery function

 group: function (text) {
        var a1 = SPM.aChart();
        var a2 = SPM.aSChart();
        $.when(a1, a2).done(function () {//excecutes below function when all the charts are loaded.
            SPM.EIEachGroup();
        });
    },

aChart and aSChart loads multiple charts and once everything is loaded i need to call EIEachGroup() function, the two functions are recursive ajax functions, currently EIEachGroup() function gets executed when the charts are not loaded, below is how i am returning promise from the ajax call.

aChart: function () {
        var grpAttrId = 1;
        var group = 'a';
        var def = $.Deferred();
        return $.ajax({
            url: Url.getSegmentBreak,
            type: 'GET',
            data: { sgmntGrpAttrId: grpAttrId, selectedGroup: group },
            dataType: 'json',
            success: function (data) {
                jQuery.each(data, function (index, item) {
                    $.ajax({
                        url: Url.GetScatterPlot,
                        type: 'GET',
                        data: { sgmntGrpAttrId: grpAttrId, selectedGroup: group, segmentBreak: item },
                        dataType: 'json',
                        success: function (data) {

                            SPM.GetSegmentScatterPlot(data,item);//function to plot chart
                        }
                    });
                });

            }
        });
        def.resolve();
        return def.promise();
    },

aSChart is also similar to above ajax, what changes has to be done to make EIEachGroup() called when both the functions are completely done.

--update - changed the function as mentioned, but still it would not wait till it finishes, below is the change i did

aMultiAxesChart: function () {
        var grpAttrId = 1
        var group = 'a'
        var def = $.Deferred();
        return $.ajax({
            url: Url.getSegmentBreak,
            type: 'GET',
            async: false,
            data: { sgmntGrpAttrId: grpAttrId, selectedGroup: group },
            dataType: 'json'

        }).done(function (data) {
            var calls = jQuery.map(data, function (item, index) {
                return $.ajax({
                    url: Url.GetHistMedTransData,
                    type: 'GET',
                    data: {
                        sgmntGrpAttrId: grpAttrId, selectedGroup: group, segmentBreak: item
                    },
                    dataType: 'json',
                    success: function (data) {
                        var seriesData = [];
                        var seriesData1 = [];
                        var seriesData2 = [];
                        var xCategories = [];

                        for (i = 0; i < data.length; i++) {
                            if (seriesData) {

                                var segName = data[i].MnthSmryStartDate;
                                if (xCategories.indexOf(segName) === -1) {
                                    xCategories[xCategories.length] = segName;
                                }
                                seriesData1[i] = data[i].MedianTransactionAmt;
                                seriesData2[i] = data[i].MedianTransactionCnt;
                            }
                        }
                        seriesData = [
                        {
                            name: 'Amount',
                            type: 'column',
                            color: '#808080',
                            data: seriesData1,
                            tooltip: {
                                valueSuffix: ' mm'
                            }

                        }, {
                            name: 'Count',
                            type: 'line',
                            color: 'Red',
                            yAxis: 1,
                            data: seriesData2,
                            tooltip: {
                                valueSuffix: ' °C'
                            }
                        }
                        ];
                        var divIndex = 1 + index;
                        var name = '#dvHistMed1' + divIndex;
                        SegmentProgressMeter.GetSegmentHistMedTransData(xCategories, seriesData, item, name);
                    }
                });
            });
            return $.when.apply($, calls);
        }).promise();
    },

Upvotes: 0

Views: 109

Answers (2)

G--
G--

Reputation: 507

var arr = [];
$.each(arr, function(i, v) {
var url = '/xml.php?id=' + v;
var xhr = $.ajax({
    url: url,
    type: 'GET',
    dataType: 'xml',
    success: function(xml) {
        if ($(xml).find('Lists').attr('total') == 1) {
            // some code here
        }
    },
    complete: function() {
        // some code here
    }
});
arr.push(xhr);
})

$.when.apply($, arr).then(function(){
console.log('done')
})

Upvotes: 0

T.J. Crowder
T.J. Crowder

Reputation: 1074138

As Grundy pointed out, you're creating a Deferred you never use. That's fine, you don't need it for anything — creating a new promise when you already have a promise to work with is an anti-pattern.

See code comments:

aChart: function () {
    var grpAttrId = 1;
    var group = 'a';
    // Return the promise we get from the deferred we get from calling
    // `done` on the first ajax call
    return $.ajax({
        url: Url.getSegmentBreak,
        type: 'GET',
        data: { sgmntGrpAttrId: grpAttrId, selectedGroup: group },
        dataType: 'json'
    }).done(function(data) {
        // First call is complete, get promises for all the subordinate parallel calls
        var calls = jQuery.map(data, function (item) { // Note that jQuery.map doesn't give you the index first
            // Return the promise here; these will be collected into
            // an array by jQuery.map
            return $.ajax({
                url: Url.GetScatterPlot,
                type: 'GET',
                data: { sgmntGrpAttrId: grpAttrId, selectedGroup: group, segmentBreak: item },
                dataType: 'json',
                success: function (data) {

                    SPM.GetSegmentScatterPlot(data,item);//function to plot chart
                }
            });
        });

        // Return a promise that waits on all of them (syntax is a bit strange, I know)
        return $.when.apply($, calls);
    }).promise(); // `done` returns Deferred, but we only want to return the Promise
},

The key to the above, and working with promises in general, is that when you call then (or done, which is a kind-of alias for then on jQuery's Deferred), the return value is a new promise (let's call it NewPromise). NewPromise will wait for the promise you called then on to be settled, then call the callback you gave done, and if that returns a promise, NewPromise waits on it; if the callback doesn't return a promise, NewPromise resolves itself using the return value as the resolution.

So:

return $.ajax(...).done(function() {
    return somePromise;
});

...will give you a promise that will wait not only for the ajax call, but also for the promise returned by the done callback.

This is a major part of the power of promises.

The reason for the weird $.when syntax is that $.when expects to receive individual promise arguments and wait for them all; it doesn't accept an array. Fortunately, JavaScript has Function#apply that we can use to call a function with an array of arguments so that the function sees them as discrete arguments. So $.when.apply($, calls) (the first argument to apply is what to use as this during the call). In ES2015, we'd be able to use $.when(...calls), but ES5 doesn't have the spread operator.

Upvotes: 1

Related Questions