1

Say for example, in my HTML file I have the following table rows in a table, where {{ }} are just variables:

<tr id="{{ object.id }}">
    <td class="name">{{ object.name }}</td>
</tr>
<tr id="{{ object.id }}">
    <td class="name">{{ object.name }}</td>
</tr>

Then in my JavaScript file I have the following code:

var rows = document.getElementsByTagName("tr"); 

for (var r = 0; r < rows.length; r++){
        var tr = rows[r];
        var id = tr.id;

        $.ajax({
            url: "/some-link/" + id,
            success: function(some_json){
                some_json = JSON.parse(some_json);
                var td = document.createElement("td");
                td.innerHTML = some_json;

                //Problem is the following tr is referring to the last tr in the for loop by the time the AJAX request is complete
                tr.appendChild(td);
            }
        });
    }

The result I get is:

<tr id="{{ object.id }}">
    <td class="name">{{ object.name }}</td>
</tr>
<tr id="{{ object.id }}">
    <td class="name">{{ object.name }}</td>
    <td>SomeJSON from when rows was 0 in the for loop</td>
    <td>SomeJSON from when rows was 1 in the for loop</td>
</tr>

However, my intended result was:

<tr id="{{ object.id }}">
    <td class="name">{{ object.name }}</td>
    <td>SomeJSON from when rows was 0 in the for loop</td>
</tr>
<tr id="{{ object.id }}">
    <td class="name">{{ object.name }}</td>
    <td>SomeJSON from when rows was 1 in the for loop</td>
</tr>

I know I can fix this by adding to the AJAX request async: false but I want to keep it asynchronous.

Any help will be much appreciated.

Tuffail
  • 77
  • 6
  • have you tried changing `for (var rows = 0; rows < tableRows.length; rows++){ var tr = tableRows[rows];` to `for (var i = 0; i < rows.length; rows++){ var tr = rows[i];`? Seems like that's what you want to do. If that doesn't work, post all of your code - `tableRows` isn't in your code. – Michael Coker Feb 25 '17 at 00:22
  • because `$.ajax` is asynchronous, by the time the success is called, tr will be `tableRows[tableRows.length - 1]` for ALL success functions – Jaromanda X Feb 25 '17 at 00:22
  • @MichaelCoker - why would changing a variable name make a difference? – Jaromanda X Feb 25 '17 at 00:23
  • @JaromandaX `rows` is all `tr`'s, then OP's using it as the counter for a loop, but referencing `tableRows`, which doesn't exist in the code. – Michael Coker Feb 25 '17 at 00:24
  • sorry, misread the code in the question :p – Jaromanda X Feb 25 '17 at 00:25
  • your code is still not going to do anything resembling useful - where is `tableRows` defined? – Jaromanda X Feb 25 '17 at 00:28
  • @Tuffail please edit your code it's accurate – Michael Coker Feb 25 '17 at 00:29
  • should `var rows = document.getElementsByTagName("tr");` be `var tableRows = document.getElementsByTagName("tr");` – Jaromanda X Feb 25 '17 at 00:30
  • I've fixed the issue, sorry I was trying to modify the code from the actual code to make it simpler to understand and I forgot to change some variable names – Tuffail Feb 25 '17 at 00:33
  • anyway, the answer below should work for you - given the appropriate changes to variable names – Jaromanda X Feb 25 '17 at 00:34
  • @JaromandaX Yes, it did indeed work. Thanks for your help – Tuffail Feb 25 '17 at 00:41

1 Answers1

1

Could it be an issue involving closures with Javascript? If you define a function inside of the for loop and assign the variables inside of the function, they will retain their scope through the success callback.

An alternate (cleaner/more elegant) solution would rewrite the for loop and function call into an Array.prototype.forEach() (thanks Jaromanda X)

We are using [] as a shorthand for accessing the Array prototype, and using .call in order to create the scoped function

Inside of the function, the variables are scoped. Previously, every ajax success callback referenced the same variable (which was the variable when the async requests returned)

[].forEach.call(rows, function(tr) { 
        var id = tr.id;

        $.ajax({
            url: "/some-link/" + id,
            success: function(some_json){
                some_json = JSON.parse(some_json);
                var td = document.createElement("td");
                td.innerHTML = some_json;

                tr.appendChild(td);
            }
        });

});
Community
  • 1
  • 1
Hodrobond
  • 1,665
  • 17
  • 18
  • 2
    an even cleaner solution is to replace the for loop with `[].forEach.call(tableRows, function(tr) { ... });` – Jaromanda X Feb 25 '17 at 00:31
  • Great idea, I just started my trek home and will edit in an hour or so, much appreciated! – Hodrobond Feb 25 '17 at 00:32
  • @Hodrobond Hey I've edited the code, I made some errors with the variable names when I tried to make the code easier to understand from my actual code. Please could you update your answer – Tuffail Feb 25 '17 at 00:35
  • Certainly, just give me about an hour or so, and I'll be back in front of a computer :) – Hodrobond Feb 25 '17 at 00:37
  • @Hodrobond Your solution has indeed worked thank you for your help – Tuffail Feb 25 '17 at 00:43
  • @Hodrobond - real programmers use mobile phones to answer SO questions :p – Jaromanda X Feb 25 '17 at 01:12
  • @JaromandaX It's a little difficult if I go into tunnels and lose connection =) . Also, answer updated (thanks again)! – Hodrobond Feb 25 '17 at 01:37