0

I'm trying to use JQuery .get() method and a javascript for loop to process some data from an external file. I've read about closures and return values from callbacks on stackoverflow for a couple hours, and i'm still confused why this isn't working.

Are the variables headers and countryData not global in scope with respect to the inner callback function? They are being assigned values as intended within the callback function, but then how do i access them once that is complete? And possibly an example wihtout using the alert() function?

function processData(inCSV){
    var headers;  
    var countryData = [];
    $.get(inCSV, function(data) {
        var lines = data.split('\r\n');
        for(var i=0;i<=lines.length-1;i++){
            var lineData = lines[i].split(',');
            if(i != 0){
                countryData[lineData[1]] = lineData.slice(2,lineData.length);
            } else {
                headers = lineData.slice(2,lineData.length);
            }
        }
        console.log('inside',headers);  // output 'inside ["1971", "1972", "1973" ...'
        console.log('inside',countryData['Brazil']);  // output 'inside ["56.4", "54.6", ..'
    });
    console.log('outside',headers);  // output 'outside undefined' ...!?
    console.log('inside',countryData['Brazil']);    // output 'outside undefined' ...!?
}   
rgdonohue
  • 1
  • 1

3 Answers3

3

The problem isn't the closure, the problem is asynchronous functions. $.get() connects to a server, and runs its callback function when the server returns an answer. But $.get() completes once the request is sent, not when the response is returned. So your last two console.log() lines are running before the callback function is executed.

You can only access the headers and countryData variables once the callback function has executed, and the only place you know that has happened is inside the callback function itself. or other code that it calls.

Ned Batchelder
  • 364,293
  • 75
  • 561
  • 662
0

$.get is asynchronous, meaning the rest of the script will not wait for it to complete. You can use the jQuery.Deferred class (docs) to mitigate this if you need more control than is offered by the success callback, or you can make the call synchronous (meaning the rest of the script will wait for it to finish before executing).

Synchronous AJAX Calls

You'll need to use $.ajax (docs), just pass-in async:false:

$.ajax({
  url: inCSV,
  async: false,
  success: function() { /* ... */ }
});

// code here will not execute until the ajax call above is complete

Deferred Objects

function processData(inCSV) {
    var deferred = jQuery.Deferred();
    $.ajax({
        url: inCSV, 
        success: function(data){
            // do stuff
            deferred.resolve([data]);
        },
        error: function() {
            deferred.reject();
        }
    });
    return deferred;
}

processingData = processData(inCSV);

// any code that doesn't care about the processing results can go here

// code that relies on headers or countryData must go in a block like this
// you can add as many "done" blocks as you like
processingData.done(function(data){
    // mess with data here
});
jchook
  • 6,690
  • 5
  • 38
  • 40
  • Thanks. I saw this example [stackoverflow] (http://stackoverflow.com/questions/1639555/jquery-return-get-data-in-a-function), post by tvanfosson, but the comments indicated that switching to synchronous is potentially problematic? – rgdonohue Jun 28 '12 at 16:26
  • Yes, it can make the script hang while the request completes or times out. You can set the `timeout` parameter to whatever you like though. I often employ a Deferred object to avoid this (my first suggestion). – jchook Jun 28 '12 at 16:31
  • Great! This worked very well (the deferred objects approach)! – rgdonohue Jun 28 '12 at 20:30
0

It's not a closure problem. It's just that the code lines doesn't execute in the order they are written.

It's a basic event programming problem: the end of the process is in the middle of the code. It's not a big problem once you're aware of it. You just have to write the end of your process at the right place.

In your case, things happen in this order:

Step 1. State variables are declared with this code:

var headers;  
var countryData = [];

Step 2. You call the server with this code

$.get(inCSV, <CALLBACK>)

At this point what's in the callback has no importance at all. It won't be executed until the server response comes back.

Step 3. You use the state variables with this code

console.log('outside',headers);  // output 'outside undefined' ...!?
console.log('inside',countryData['Brazil']);    // output 'outside undefined' ...!?

They are undefined, which is perfectly expectable because no code initialized them.

Step 4. Response comes back from the server:

    var lines = data.split('\r\n');
    for(var i=0;i<=lines.length-1;i++){
        var lineData = lines[i].split(',');
        if(i != 0){
            countryData[lineData[1]] = lineData.slice(2,lineData.length);
        } else {
            headers = lineData.slice(2,lineData.length);
        }
    }
    console.log('inside',headers);  // output 'inside ["1971", "1972", "1973" ...'
    console.log('inside',countryData['Brazil']);  // output 'inside ["56.4", "54.6", ..'
Samuel Rossille
  • 18,940
  • 18
  • 62
  • 90
  • Great. I understand the problem now. What do I lose then by making the call synchronous instead (that isn't the point of using ajax, no?)? – rgdonohue Jun 28 '12 at 16:37
  • The A of AJAX is for asynchronous! There is a huge win in asynchronism: your main process is not stuck while you wait for data, which means, that your user interface always stays fluid and reactive, even if you have a spinner at a location which is loading. For example, if you draw 3 widgets on your page, and each widget fetches data, each widget lives his life without stucking the whole page. The small counterpart is that asynchronous code makes code just a little harder to read when you're not yet used to it. – Samuel Rossille Jun 28 '12 at 16:44