2

I'm doing an AJAX call to retrieve all stores in a certain state. The problem is, is that the last 2 lines can't access any of the variable content that I built up in the return function from the get call.

    var states = ['TX', 'AZ];

    for(j = 0; j < states.length; j++) {
        var fullHTML = '';
        var full2;
        $.get("//127.0.0.1:8000/api/state-stores/", {state: states[j]}, function(data) {
            console.log(data.stores.length);

            for(var i = 0; i < data.stores.length; i++) {
                store_html = '<tr><td>' + data.stores[i].name + '</td><td>'
                fullHTML += store_html;
            }
        })
        // PROBLEM HERE
        console.log(fullHTML); //empty
        $("#" + states[j].toLowerCase() + "-table").html(fullHTML); //does nothing
    }

What can I do to fix this? I've been trying weird global variable tricks and all sorts of hack-y things but nothing's working.

UPDATE I also tried placing my last two lines inside the $.get call but states[j] is for some reason inaccessible inside the loop. After debugging, it seems like just j from the outer loop is inaccessible inside this return function. Is there a way to fix that?


UPDATE 2

I followed the advice given in the answer and I am getting properly concatenated strings for fullHTML, but, each fullHTML string is replacing only the last state in the table. It's as if the TX tag is the only one receiving all the updates. I think this is closure-based but I don't know how to resolve this.

$(function() {
    var states = ['AZ', 'CA', 'CO', 'GA', 'NV', 'NC', 'SC', 'TX'];
    // var states = ['CA'];
    for(var j = 0; j < states.length; j++) {
        var current = j;
        $.get("//127.0.0.1:8000/api/state-stores/", {state: states[j]}, function(data) {
            var fullHTML = '';
            for(var i = 0; i < data.stores.length; i++) {
                store_html = '<tr><td>' + data.stores[i].name + '</td><td>' + data.stores[i].address + '<span class="small-table"><br>' + data.stores[i].city + '</span></td><td>' + data.stores[i].city + '</td><td>' + data.stores[i].state + '</td><td>' + data.stores[i].postal_code + '</td></tr>'
                fullHTML += store_html;
            }
            $('#' + states[current].toLowerCase() + '-table').html(fullHTML);
        })  
    }

});
qarthandso
  • 2,100
  • 2
  • 24
  • 40
  • 2
    It's not _stuck_ in loop, it about timing. You're accessing the variable before the data is loaded from the server and assigned to variable. **That's ok, but how to solve this?** Just move the last statement inside the `$.get()` callback. – Tushar Aug 11 '16 at 15:22
  • @Tushar please see my update to my question as to why I wanted to do it this way but ran into a problem. – qarthandso Aug 11 '16 at 15:28
  • @qarthandso See my edit for a working solution to your `Update 2`. Also, I'm not sure why this was marked as duplicate (to that question in particular, as that's not what you were asking, but this [was asked before](http://stackoverflow.com/questions/7053965/when-using-callbacks-inside-a-loop-in-javascript-is-there-any-way-to-save-a-var) somewhere else, but the duplicate isn't pointing to a relevant question.) – Patrick Bell Aug 11 '16 at 15:49

1 Answers1

2

That is an asynchronous $.get function. That is to say, code execution will not wait at that line until the $.get is done, perform the callback function, and then continue onto the next line after the $.get. In reality, it will perform an asynchronous call to the $.get function, and continue execution immediately to the console.log(fullHTML) line. There is no guarantee at this point that the $.get function has finished or even started, as it's being executed asynchronously.

As for your issue with the accessibility to j, that's a problem that comes back to closures. Your j variable, when accessed inside of the $.get callback, will reference the j variable that has been looped through already, and will return the incorrect j value, as you probably want the value of j from when the $.get request was executed. So, you'll want to store j inside of the local closure by declaring it explicitly like var current = j, the instead of referencing j inside of your $.get callback, you'd reference current, and it would perform as intended. This doesn't work!

EDIT

We can force create a new scope if we declare an anonymous function and execute the $.get inside of that context. It's not a pretty solution, but it works!

To fix your code, put your calls inside of the $.get callback like so:

var states = ['TX', 'AZ'];

for(j = 0; j < states.length; j++) {
    var fullHTML = '';
    var full2;
    (function() {
        var current = j;
        $.get("//127.0.0.1:8000/api/state-stores/", {state: states[j]}, function(data) {
            console.log(data.stores.length);

            for(var i = 0; i < data.stores.length; i++) {
                store_html = '<tr><td>' + data.stores[i].name + '</td><td>'
                fullHTML += store_html;
            }
            console.log(fullHTML); // not empty anymore
            $("#" + states[current].toLowerCase() + "-table").html(fullHTML); //does stuff
        });
   })();
}

Now, your code will only perform the lines that were broken before AFTER it has populated the fullHTML variable.

Patrick Bell
  • 769
  • 3
  • 15
  • I wanted to do it this way but also ran into a problem. Please see my updated question for why this method didn't work for me. – qarthandso Aug 11 '16 at 15:27
  • @qarthandso Updated my answer to address that. – Patrick Bell Aug 11 '16 at 15:30
  • This is the exact solution. Thank you so much. Why is this IIFE necessary? to store the current `state` value? – qarthandso Aug 11 '16 at 15:50
  • 1
    I'm no JS expert, so I may be spewing nonsense, but I believe it's because we have to force declare a new closure that can't have it's contents touched by the context the `for` loop is running in (either `window` or possibly whatever context is denoted by `$`). Declaring an IIFE gives us just that. – Patrick Bell Aug 11 '16 at 15:53