1

On my code I hava a function with 3 nested AJAX calls, in order for it to work I had to set Async=false.

As I have read that Async=false is deprecated I replaced the Async=false with promises.

This is my function before I edited it:

self.getOrders = function (name) {
    var orders= [];
    var order= function (item, type1, type2) {
        var self = this;
        self.order= item;
        self.type1= type1;
        self.type2= type2;
    }

$.ajax({
    url: "/API/orders/" + name,
    type: "GET",
    async: false,
    success: function (orderResults) {
        var mappedOrders = $.map(orderResults, function (orderItem) {
        $.ajax({
            url: "/API/orders/property/" + orderItem.id + "/type1",
            type: "GET",
            async: false,
            success: function (property1Results) {
                 $.ajax({
                     url: "/API/orders/property/" + orderItem.id + "/type2",
                     type: "GET",
                     async: false,
                     success: function (property2Results) {
                          orders.push(new order(orderItem, property1Results, property2Results));
                        }
                    });
                  }
                });
            })
        }
    });
    return orders;

This function worked perfectly, I got the data end everything worked fine.

Then I changed the function to use promises instead of Async=false, this is the edited function, with promises:

//The begin of the function- same as first one
var orders= [];
var firstPromise = $.ajax({
        url: "/API/orders/" + name,
        type: "GET"
    });
    $.when(firstPromise).done(function (orderResults) {
        var mappedOrders = $.map(orderResults, function (orderItem) {
            var secondPromise = $.ajax({
                url: "/API/orders/property/" + orderItem.id + "/type1",
                type: "GET"
            });
            $.when(secondPromise).done(function (property1Results) {
                var thirdPromise = $.ajax({
                    url: "/API/orders/property/" + orderItem.id + "/type2",
                    type: "GET"
                });
                $.when(thirdPromise).done(function (property2Results) {
                    orders.push(new order(orderItem, property1Results, property2Results));
                });
            });
        });
    });
    return orders;

And the function call:

self.populateOrders = function (name) {
    var mappedOrders = $.map(self.service.getOrders(name), function (item) {
        return new Order(item)
        });
    self.orders(mappedOrders);
}

The new function is not working, I'm getting back from the firstPromise a wrong json with backslashes, and the returned orders object is empty.

Any idea what am I doing wrong? I spent so much time on it but couldn't figure it out.

Thanks in advance.

user3378165
  • 6,546
  • 17
  • 62
  • 101

2 Answers2

0

Nested ajax calls inside a loop is a hell to manage. You can do it like this.

  • Create a promise to notify the caller when the whole process is finished
  • Wait for all the inner ajax calls to resolve
  • Resolve you main promise to notify the caller

 

self.getOrders = function (name) {
    var mainDeferred = $.Deferred();
    var orders = [];
    var order = function (item, type1, type2) {
        var self = this;
        self.order = item;
        self.type1 = type1;
        self.type2 = type2;
    }

    $.ajax({
        url: "/API/orders/" + name,
        type: "GET",
        success: function (orderResults) {
            var innerwait = [];

            var mappedOrders = $.map(orderResults, function (orderItem) {
                var ajax1 = $.ajax({
                    url: "/API/orders/property/" + orderItem.id + "/type1",
                    type: "GET"
                });
                var ajax2 = $.ajax({
                    url: "/API/orders/property/" + orderItem.id + "/type2",
                    type: "GET"
                });
                $.when(ajax1, ajax2).done(function (property1Results, property2Results) {  
                    orders.push(new order(orderItem, property1Results[0], property2Results[0])))
                });
                innerwait.push(ajax1, ajax2);
            });;

            $.when.apply(null, innerwait) //make sure to wait for all ajax requests to finish
                .done(function () {
                    mainDeferred.resolve(orders); //now that we are sure the orders array is filled, we can resolve mainDeferred with orders array
                });
        }
    });

    return mainDeferred.promise();
}


self.populateOrders = function (name) {
    self.service.getOrders(name).done(function (orders) { //use .done() method to wait for the .getOrders() to resolve
        var mappedOrders = $.map(orders, function (item) {
            return new Order(item)
        });
        self.orders(mappedOrders);
    });
}

In the example, note that I use $.when.apply() to wait for an array of deferreds.

Ozan
  • 3,709
  • 2
  • 18
  • 23
  • Though this should work, note that i could not test this in a jsfiddle since you didn't provide one. If you have problems with it, wrap them up in a jsfiddle and I'll see about fixing it. – Ozan Aug 12 '16 at 07:53
  • @Bergi's comment made me realize your ajax inner ajax calls did not need to be nested. This somewhat simplifies the code so I edited accordingly. – Ozan Aug 12 '16 at 08:03
  • Thank you very much for your clear answer, I will try it now and will let you know, Thank you! – user3378165 Aug 12 '16 at 11:17
  • This nearly is a good answer, but you should avoid the [deferred antipattern](http://stackoverflow.com/q/23803743/1048572)! No need to "notify" anything if you'd just use `then` for chaining. Avoid `done` and `success` callbacks! – Bergi Aug 12 '16 at 11:24
  • @Ozan It's working but not completely, I'm getting the data and I don't get any console errors, but the data coming back from `ajax1` and `ajax2` is weird, with `ko.toJSON` it looks like: `[[{"address":"test","city":"test","state":"NY","zip":null}],"success",{"readyState":4,"responseText":"[\r\n {\r\n \"address\": \"test\",\r\n \"city\": \"test\",\r\n \"state\": \"NY\",\r\n \"zip\": null\r\n }\r\n]","responseJSON"}]`etc. And on the page it looks like this:`[object Object]s, u, c, c, e, s, s 4, function (a){var b;if(2===t){if(!j){j=` etc.. Any idea what causes it? Thanks so much!! – user3378165 Aug 12 '16 at 11:58
  • @Bergi, true you could simplify this further with chaining. But I personally find it easier to manage with having that one *main deferred*, and then shortening the code inside the function as much as I want. This allows me to rosolve the whole thing from anywhere inside the function without concerning myself with proper return values. I think it's fine as long as you don't make it a habit to create unnecessary promises. **user3378165**, I'll look into the problem when I get to a computer. – Ozan Aug 12 '16 at 14:28
  • @Ozan: The problem is not that it's unnecessary, the problem is that it doesn't work: errors are not propagated for example. This is what simple works when you implement it properly with return values. Try it, it doesn't even look much different. – Bergi Aug 12 '16 at 14:37
  • @user3378165, it was a simple mistake. `$.when` assign an array of values to the `done callback` for ajax values that is `[data, status, jqxhr]`, so we just need to put first item of the array instead of the array itself. Changed `orders.push(new order(orderItem, property1Results, property2Results))` to `orders.push(new order(orderItem, property1Results[0], property2Results[0]))`. This should get you the data correctly. – Ozan Aug 12 '16 at 20:18
  • @Bergi, I understand what you are suggesting, I just prefer to have that one extra deferred in cases like this. This does work and it is not really all that different from what you are suggesting either, only has that extra deferred. Only reason it is not rejecting on error is because i did not implement it, since the OP didn't have errors handled in their code. – Ozan Aug 12 '16 at 20:22
  • @Ozan: "*because i did not implement it*" - yes, that's exactly the problem with this style. If done properly, you would not need to think about it. – Bergi Aug 13 '16 at 10:23
  • You can just forgo error callbacks when chaining with then too. I didn't put error handling in because OP's code wasn't concerned about error handling. Literally only difference between the two methods is an extra deferred, I am not even claiming my method is the absolute right way to do it or anything. You can do it either way and it would work with no noticeable difference. It is just a preference. It definitely is not deferred antipattern though. – Ozan Aug 13 '16 at 10:32
  • @Ozan Amazing!! That was the issue, it's working perfectly, thank you so much!!! You are a genius, I wish I could vote more than once! Thank you!! – user3378165 Aug 14 '16 at 11:23
-4

A bug recently introduced in chrome 52 (august 2016) can cause this kind of behavior : answers for nested requested are ignored. Hoppefully, it will not last long.

https://bugs.chromium.org/p/chromium/issues/detail?id=633696

Try to add cache: false