Cuddl3s
Cuddl3s

Reputation: 163

$.each in $.getJSON race condition?

I have a problem when using the jquery.each() function within a jquery.getJSON. There's 2 types of element's I'm dealing with in my code in this case. "Sources" and "Streams" I want to use getJSON to first get the sources, iterate over them and generate an accordion header out of those. Then, for each of those "sources" I again use getJSON with that source's id to get it's corresponding "streams". Then I append those streams to it's sources accordion body, to get a list of all the streams, sorted by their sources.

But it seems while I'm getting the JSON, the next statements in my procedure are already executed. As I'm basically dynamically building a large HTML String and adding it to an element using jQuery, the String doesn't get all the data it needs.

The code looks as follows:

var html1 = "<div class='panel-group' id='manageAccordion'>";
$.getJSON(url, function(data){
    $.each(data, function(i, json){
        html1 += getAccordionPart(...); //creates the accordion for the given source
    });

}).done(function(){
    html1 += "</div>";
    $('#elementList').html(html);
});

function getAccordionPart(id, parent, count, json){ 
    //new string html2 is created and a bunch of stuff added
    //...
    html2 += "...."; 
    html2 += getAccordionBody(json);
    html2 += "</div></div></div></div>";
    return html2
}

function getAccordionBody(json){
//new string "html3" gets created
//...

 var url = standardUrl + "sources/" + encodeURIComponent(json.elementId) + "/streams";
 $.getJSON(url, function(data) {
    $.each(data, function(i, json) {
        html3 += "<li class='list-group-item'>";
        html3 += json.name;
        html3 += "</li>";
    });

}).done(function(){
    html3 += "</ul>";
    return html3;
});

}

What I specifically end up with is an accordion header that has "undefined" in it's body, because it seems the getAccordionBody() function doesn't return before the html string gets added to the DOM.

I already tried changing the $.getJSON to $.ajax with async = false, on both of my $.getJSON calls, this seems to fix the problem that the statements don't execute in the order I want them to, but it's horribly slow and returns undefined anyway for some reason..

Any suggestions? Am I missing something really stupid?

Upvotes: 1

Views: 493

Answers (3)

Hassan Baig
Hassan Baig

Reputation: 346

You have to first collect everything which is required for "getAccordionPart" and "getAccordionBody" (eg: encodeURIComponent(json.elementId)) in a global array. like:

var arrIds = [];

$.getJSON(url, function(data){
$.each(data, function(i, json){
    arrIds.push(encodeURIComponent(json.elementId))//collect everything which is required for "getAccordionPart" and "getAccordionBody"
});

then Iterate on this collection and collect all the data for accordian body of these ids in any global variable.

var bodyData = {};
$.each(arrIds, function(){
var url = standardUrl + "sources/" + this + "/streams";
$.getJSON(url, function(data) {
    bodyData[this] = data;//collect all the Data for accordian body of these ids
}});

Also do a litle change in getAccordionPart like:

function getAccordionPart(id, parent, count, /*json*/ elementId){ 
//new string html2 is created and a bunch of stuff added
//...
html2 += "...."; 
html2 += getAccordionBody(elementId);
html2 += "</div></div></div></div>";
return html2
}

also change getAccordionBody like:

    function getAccordionBody(/*json*/ elementId){
//new string "html3" gets created
//...

 var data = bodyData[elementId];
    $.each(data, function(i, json) {
        html3 += "<li class='list-group-item'>";
        html3 += json.name;
        html3 += "</li>";
    });
html3 += "</ul>";
    return html3;
}

Your Calling will also be changed:

$.each(arrIds , function(){
    html1 += getAccordionPart(,,,this); //creates the accordion for the given source
});

You Final code will be:

var arrIds = [];

$.getJSON(url, function(data){
$.each(data, function(i, json){
    arrIds.push(encodeURIComponent(json.elementId))//collect everything which is required for "getAccordionPart" and "getAccordionBody"
});

var bodyData = {};
$.each(arrIds, function(){
var url = standardUrl + "sources/" + this + "/streams";
$.getJSON(url, function(data) {
    bodyData[this] = data;//collect all the Data for accordian body of these ids
}});

function getAccordionPart(id, parent, count, /*json*/ elementId){ 
    //new string html2 is created and a bunch of stuff added
    //...
    html2 += "...."; 
    html2 += getAccordionBody(elementId);
    html2 += "</div></div></div></div>";
    return html2
    }

function getAccordionBody(/*json*/ elementId){
//new string "html3" gets created
//...

 var data = bodyData[elementId];
    $.each(data, function(i, json) {
        html3 += "<li class='list-group-item'>";
        html3 += json.name;
        html3 += "</li>";
    });
html3 += "</ul>";
    return html3;
}

$.each(arrIds , function(){
    html1 += getAccordionPart(,,,this); //creates the accordion for the given source
});

Upvotes: 0

Bergi
Bergi

Reputation: 664620

it seems the getAccordionBody() function doesn't return before the html string gets added to the DOM

That's correct. Your way of returning the response (html3) from ajax is flawed - you cannot return from a done handler.

You can however fix this by using promises. Every function returns a promise so that they are easily chained, and then is used to transform the data and get a new promise for it:

$.getJSON(url).then(function(data){
    return $.when.apply($, $.map(data, function(i, json){
        return getAccordionPart(…); //creates the accordion for the given source
    }).then(function() {
        var html1 = "<div class='panel-group' id='manageAccordion'>";
        html1 += Array.prototype.join.call(arguments, "\n");
        html1 += "</div>";
        return html1;
    });
}).done(function(html){
    $('#elementList').html(html);
});

function getAccordionPart(id, parent, count, json){ 
    return getAccordionBody(json).then(function(result) {
        var html2 = "…"; //new string html2 is created and a bunch of stuff added
        html2 += result;
        html2 += "</div></div></div></div>";
        return html2;
    });
}

function getAccordionBody(json) {
    var url = standardUrl + "sources/" + encodeURIComponent(json.elementId) + "/streams";
    return $.getJSON(url).then(function(data) {
        var html3 = "…"; //new string "html3" gets created
        $.each(data, function(i, json) {
            html3 += "<li class='list-group-item'>";
            html3 += json.name;
            html3 += "</li>";
        });
        html3 += "</ul>";
        return html3;
    });
}

Upvotes: 1

Alphonso
Alphonso

Reputation: 1295

Rather than using sync calls you can use the Deferred / promise pattern - http://api.jquery.com/deferred.promise/.

  • You will need to return promise from generate accordion function.
  • You will need to queue up your generate accordion function calls.
  • And then do the appending business sequentially as and when promises are resolved.

Upvotes: 0

Related Questions