1

I need to pull data from a series of .csv files off the server. I am converting the csvs into arrays and I am trying to keep them all in an object. The ajax requests are all successful, but for some reason only the data from the last request ends up in the object. Here is my code:

var populate_chart_data = function(){
    "use strict";
    var genders = ["Boys","Girls"];
    var charts = {
        WHO: ["HCFA", "IWFA", "LFA", "WFA", "WFL"],
        CDC: ["BMIAGE", "HCA", "IWFA", "LFA", "SFA", "WFA", "WFL", "WFS"]
    };
    var fileName, fileString;
    var chart_data = {};
    for (var i=0; i < genders.length; i++){
        for (var item in charts){
            if (charts.hasOwnProperty(item)){
                for (var j=0; j<charts[item].length; j++) {
                    fileName = genders[i] + '_' + item + '_' + charts[item][j];
                    fileString = pathString + fileName + '.csv';
                    $.ajax(fileString, {
                        success: function(data) {
                            chart_data[fileName] = csvToArray(data);
                        },
                        error: function() {
                            console.log("Failed to retrieve csv");
                        },
                        timeout: 300000
                    });
                }
            }
        }
    }
    return chart_data;
};
var chart_data = populate_chart_data();

The console in Firebug shows every ajax request successful, but when I step through the loops, my chart_data object is empty until the final loop. This is my first foray into ajax. Is it a timing issue?

burgerB
  • 762
  • 1
  • 8
  • 18
  • 1
    I think you might need to lookup the concept of closures http://stackoverflow.com/questions/111102/how-do-javascript-closures-work – TommyBs Apr 26 '13 at 15:41
  • It shouldn't be a timing issue if `fileName` is different, but you can test this to make sure by settings `async: false` in your ajax call. – nullability Apr 26 '13 at 15:43
  • 1
    You may want to read http://stackoverflow.com/questions/750486 - the value of `fileName` isn't what you think it is, because you have a closure nested in a loop. – RichieHindle Apr 26 '13 at 15:44
  • Thanks guys, you were right. I found this [article on closures](http://javascriptweblog.wordpress.com/2010/10/25/understanding-javascript-closures/) in one of the SO questions you posted and it really cleared things up for me: – burgerB Apr 26 '13 at 16:25

2 Answers2

2

There are two things you need to consider here:

  1. The AJAX calls are asynchronous, this means you callback will only be called as soon as you receive the data. Meanwhile your loop keeps going and queueing new requests.

  2. Since you're loop is going on, the value of filename will change before your callback is executed.

So you need to do two things:

  1. Push the requests into an array and only return when the array completes
  2. Create a closure so your filename doesn't change

.

var chart_data = [];
var requests = [];

for (var j=0; j<charts[item].length; j++) {
    fileName = genders[i] + '_' + item + '_' + charts[item][j];
    fileString = pathString + fileName + '.csv';
    var onSuccess = (function(filenameinclosure){               // closure for your filename
                        return function(data){
                            chart_data[filenameinclosure] = csvToArray(data);
                        };
                    })(fileName);
    requests.push(                                     // saving requests
        $.ajax(fileString, {
            success: onSuccess,
            error: function() {
                console.log("Failed to retrieve csv");
            },
            timeout: 300000
        })
    );
}

$.when.apply(undefined, requests).done(function () {
    // chart_data is filled up
});
Kenneth
  • 28,294
  • 6
  • 61
  • 84
  • Thanks @Kenneth, that introduced me to a number of great concepts and handled the issue. Well appreciated. – burgerB Apr 26 '13 at 16:25
  • No problem, happy to help. Please remember to accept / up vote the answer if it has helped you – Kenneth Apr 26 '13 at 16:42
  • Actually there is part I am still confused about. The definition of the closure has `(filename)` at the end. I get an error because that is not yet defined. I am not really clear on what that is supposed to do. Also, should `var onSuccess = (function(filename){` be `var onSuccess = (function(fileName){` instead? Am I defining a parameter for that anonymous function or passing in a parameter? – burgerB Apr 26 '13 at 18:22
  • I edited my answer and changed some variable names so it's not that confusing anymore. Essentially what you do in that line is create a function and immediately call it, passing in `fileName` as a parameter. Then you assign the returned value to `onSuccess`. The function effectively returns another function, with the benefit that inside that `function`you have a reference to a parameter (which you passed in before). Hope that clears it up a bit – Kenneth Apr 26 '13 at 18:33
1

I'm surprised that any data ends up in the object. The thing about ajax is that you can't depend on ever knowing when the request will complete (or if it even will complete). Therefore any work that depends on the retrieved data must be done in the ajax callbacks. You could so something like this:

var requests = [];
var chart_data = {};
/* snip */
requests.push($.ajax(fileString, {
/* snip */

$.when.apply(undefined, requests).done(function () {
    //chart_data should be full
});
Explosion Pills
  • 188,624
  • 52
  • 326
  • 405