2
\$\begingroup\$

Following is a working solution for a single xml ajax to retrieve category list, then individual ajax calls for each category. This was developed in response to a question on SO.

Solution creates one deffered for the category ajax call, and array of deffereds for the items ajax calls. Bottom of code nests $when(itemsCallsArray) inside $.when( catgoryCall)

Working demo

Categories should stay in order of xml but looking for any improvements that could be used to keep the items calls in order.

/* store all ajax content in object*/
var content = {};

function parseContent(content) {
    $.each(content, function() {
        $('body').append('<br><br>' + this.a + '<br>' + this.b.join('<br>'));
    })
}
/* array to store nested ajax promises */
var deferreds_b = [];
/* A promise and ajax*/    
var xml_A = $.post("/echo/xml/", {
    xml: a
}, function(returnedXML) {

    var $el_A = $(returnedXML).find("ElementA");
    var total_A = $el_A.length;
    $el_A.each(function(counter) {
        var countA = counter;
        var name_of_Attribute_of_Element_A = $(this).attr("name");
        /* create EL A object , store EL A content, create array to push B content to */
        content[countA] = {
            a: countA + ". " + name_of_Attribute_of_Element_A,
            b: []
        }

        var xml_B = $.post("/echo/xml/", {
            xml: b
        }, function(returnedXML) {

            var $el_B = $(returnedXML).find("ElementB");
            var total_B = $el_B.length;
            $el_B.each(function(idx) {
                var name_of_Attribute_of_Element_B = $(this).attr("name");
                content[countA].b.push(countA + ". " + name_of_Attribute_of_Element_B);
            });
        });
    /* push B promise to array*/
        deferreds_b.push(xml_B);


    });
});

$.when(xml_A).done(function() {
    /* when A is done , wait for B to be done*/
    $.when.apply(null, deferreds_b).done(function() {
        parseContent(content);
        $("body").append("<p>All done!</p>");
    });

})
\$\endgroup\$

2 Answers 2

2
\$\begingroup\$

I'm not sure what do you exactly need? http://jsfiddle.net/tomalec/GeRkG/1/

  • Use promise returned by $.post. we don't need var xml_A and $.when(xml_A)
  • Remove deffereds_b from global scope, and use it as resolved value.

And now improvements depends on what you are trying to achive:

  1. Just get all responses together in right order, after all is done
  2. Do above, but performe partial operations as soon as posible, if so do you really need countA while handling B response?

1: http://jsfiddle.net/tomalec/GeRkG/3/ - Move B response handling outside - Move content to local scopes or even: http://jsfiddle.net/tomalec/GeRkG/4/ - one callback, and object creation less


2: http://jsfiddle.net/tomalec/rwq2d/2/ - Move AB response handling outside, we can move A separately if you want to perform it sooner, than later (before B response). - Move content to local scopes - Removed closure for $.when.apply( null, requests)

function parseContent(content) {
    console.log('content',content);
    $.each(content, function() {
        $('body').append('<br><br>' + this.a + '<br>' + this.b.join('<br>'));
    })
}
function handleABResponse(returnedXML, $el_A, countA) {
    var name_of_Attribute_of_Element_A = $el_A.attr("name"),
        $el_Bs = $(returnedXML).find("ElementB"),
        results_b = [];
    $el_Bs.each(function(index, el) {
        var name_of_Attribute_of_Element_B = $(el).attr("name");
        results_b.push (countA + ". " + name_of_Attribute_of_Element_B);
    });

    return {
        a: countA + ". " + name_of_Attribute_of_Element_A,
        b: results_b
    }
}
/* Request As */  
$.post("/echo/xml/", {
    xml: a
}).pipe(function(returnedXML) {
    /* process As */
    /* array to store nested ajax promises */
    var requests = [];

    var $el_As = $(returnedXML).find("ElementA");
    $el_As.each(function(countA) {
        var $el_A = $(this);
        /* push B promise to array*/
        requests.push(
            $.post("/echo/xml/", {
                xml: b
            })
            .pipe(function bSuccess (response){
                /* process A and Bs  */
                return handleABResponse(response, $el_A, countA);
            })
        );
    });
    /* return promise for all requests */
    return $.when.apply( null, requests );
}).done(function() { //content[0], content[1],..
    /* collect processed responses */
    console.warn(this,arguments);
    parseContent(arguments);
    $("body").append("<p>All done!</p>");
});
\$\endgroup\$
0
\$\begingroup\$

If you just want to simplify the javascript without really changing anything, then it will boil down from about 38 active lines to 21 as follows :

$.post("/echo/xml/", { xml: a }).done(function(returnedXML) {
    var content = [], promises = [];
    $(returnedXML).find("ElementA").each(function(counter) {
        var obj = {
            a: counter + ". " + $(this).attr('name'),
            b: []
        };
        content.push(obj);
        promises.push($.post("/echo/xml/", { xml: b }).done(function(returnedXML) {
            $(returnedXML).find("ElementB").each(function() {
                obj.b.push(counter + ". " + $(this).attr('name'));
            });
        }));
    });
    $.when.apply(null, promises).done(function() {
        $.each(content, function() {
            $('body').append('<br><br>' + this.a + '<br>' + this.b.join('<br>'));
        });
        $("body").append("<p>All done!</p>");
    });
});

DEMO

If you only ever want to display text, and don't need to keep the jqXHR promises for any other purpose, then you can put the text straight into the DOM as the responses arrive rather than using javascript objects/arrays for intermediate storage :

$.post("/echo/xml/", { xml: a }).done(function(returnedXML) {
    $(returnedXML).find("ElementA").each(function(i) {
        var $div = $("<div/>").addClass('A_wrapper').appendTo("body").append($("<div/>").addClass('A_text').text(i + '. ' + $(this).attr('name')));
        $.post("/echo/xml/", { xml: b }).done(function(returnedXML) {
            $(returnedXML).find("ElementB").each(function() {
                $div.append($("<div/>").addClass('B_text').text(i + '. ' + $(this).attr('name')));
            });
        });
    });
});

Just 10 lines

The key here is to exploit the closure formed by the outer .each() callback, to keep the reference $div, which remains available to the inner .each() callback, thus putting the B-elements under their corresponding A-element.

This second approach offers the (possible) advantage of not waiting for all the responses to arrive before starting to display the text. You would deny yourself the opportunity of knowing when everything is "all done" though, if required, this feature could be added back in with another few lines of code.

DEMO (with a little CSS for additional styling)

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.