views:

773

answers:

4

This may be more of a scoping question. I'm trying to set a JSON object within a $.getJSON function, but I need to be able to use that object outside of the callback.

var jsonIssues = {};  // declare json variable

$.getJSON("url", function(data) {
    jsonIssues = data.Issues;
});

// jsonIssues not accessible here

A similar question like this one was asked in another post, and the consensus was that anything I need to do with the JSON objects needs to be done within the callback function, and cannot be accessed anywhere else. Is there really no way that I can continue to access/manipulate that JSON object outside of the $.getJSON callback? What about returning the variable, or setting a global?

I'd appreciate any help. This just doesn't seem right...

UPDATE:

Tried setting the $.ajax() async setting to false, and running through the same code, with no luck. Code I tried is below:

var jsonIssues = {};  // declare json variable

$.ajax({ async: false });

$.getJSON("url", function(data) {
    jsonIssues = data.Issues;
});

// jsonIssues still not accessible here

Also, I've had a couple responses that a global variable should work fine. I should clarify that all of this code is within $(document).ready(function() {. To set a global variable, should I just declare it before the document.ready? As such:

var jsonIssues = {};

$(document).ready(function() {

  var jsonIssues = {};  // declare json variable

  $.getJSON("url", function(data) {
      jsonIssues = data.Issues;
  });

  // now accessible?
}

I was under the impression that that a variable declared within document.ready should be "globally" accessible and modifiable within any part of document.ready, including subfunctions like the $.getJSON callback function. I may need to read up on javascript variable scoping, but there doesn't seem to be an easy to achieve what I'm going for. Thanks for all the responses.

UPDATE #2: Per comments given to answers below, I did use $.ajax instead of .getJSON, and achieved the results I wanted. Code is below:

var jsonIssues = {};
    $.ajax({
     url: "url",
     async: false,
     dataType: 'json',
     success: function(data) {
      jsonIssues = data.Issues;
     }
    });

    // jsonIssues accessible here -- good!!

Couple follow-up comments to my answers (and I appreciate them all). My purpose in doing this is to load a JSON object initially with a list of Issues that the user can then remove from, and save off. But this is done via subsequent interactions on the page, and I cannot foresee what the user will want to do with the JSON object within the callback. Hence the need to make it accessible once the callback complete. Does anyone see a flaw in my logic here? Seriously, because there may be something I'm not seeing...

Also, I was reading through the .ajax() jQuery documentation, and it says that setting async to false "Loads data synchronously. Blocks the browser while the requests is active. It is better to block user interaction by other means when synchronization is necessary."

Does anyone have an idea how I should be blocking user interaction while this is going on? Why is it such a concern? Thanks again for all the responses.

+2  A: 

$.getJSON is asynchronous. That is, the code after the call is executed while $.getJSON fetches and parses the data and calls your callback.

So, given this:

a();

$.getJSON("url", function() {
    b();
});

c();

The order of the calls of a, b, and c may be either a b c (what you want, in this case) or a c b (more likely to actually happen).

The solution?

Synchronous XHR requests

Make the request synchronous instead of asynchronous:

a();

$.ajax({
    async: false,
    url: "url",
    success: function() {
        b();
    }
});

c();

Restructure code

Move the call to c after the call to b:

a();

$.getJSON("url", function() {
    b();

    c();
});
strager
Thanks strager for the quick reply. You'll have to forgive me.. even with a CS degree, I'm still kind of a noob at some of this stuff. I think I've seen that you can set async to false in the $.ajax(..)? Can I just say "$.ajax({async: false});"? Also, and maybe more importantly, what will it means once asynchronous is turned off? Everything executes top to bottom? Thanks again..
Mega Matt
A sound answer. Unfortunately (as the OP has discovered in the question update), `$.getJSON` does not provide an "option override" for the `asynch` option. @Mega Matt: you'll have to call `$.ajax` *instead of* `$.getJSON`, passing `dataType:"json"` in order to perform the same work of getJSON.
Crescent Fresh
There's no good reason to be synchronous rather than async. Just put the code into the callback.
Nosredna
Woops, I meant `async`, not `asynch`. Another option that comes to mind is to set a "global" override via `$.ajaxSettings.async = false`. Note that this affects all XMLHttpRequests.
Crescent Fresh
@Nosredna, I have to disagree. Most file I/O is done synchronously where it could be more efficient asynchronously, and yet the synchronous approach is almost always taken. Granted, network I/O is generally slower, but translating and understanding and maintaining single-threaded code is often easier than multi-threaded code. @Fresh, you are correct; I would use `$.ajax` if I needed to make a synchronous XHR call.
strager
I didn't mean always, @strager, I meant in JavaScript Ajax programs. $document ready is meant for setting up things like click handlers. Doing stuff sync instead of async in JavaScript is a bad idea because JS is single threaded and the thread can't end. The interface gums up and some browsers will throw up alerts about the script not ending. Since the goal of learning Ajax is usually to create responsive web pages or RIAs (not command line apps), async is what's needed to be learned.
Nosredna
@Nosredna, I agree mostly. There are still some cases when using synchronous isn't bad, e.g. when you're already in a new thread.
strager
Sure, but it's the exception in JavaScript. Believe me there are many times I've WANTED sync. :-)
Nosredna
A: 

You are just running into scoping issues.

Short answer:

window.jsonIssues = {};  // or tack on to some other accessible var

$.getJSON("url", function(data) {
    window.jsonIssues = data.Issues;
});

// see results here
alert(window.jsonIssues);

Long answers:

http://stackoverflow.com/questions/327454/scoping-issue-in-javascript http://stackoverflow.com/questions/1046332/javascript-closure-scoping-issue

johnvey
Unless I'm misinterpreting your short answer, using "window." in front of the variables is not working. Were you just trying to make a scoping point?
Mega Matt
No, this is not a scoping issue, this is an asynchronous issue. Tacking on `window.` in front of everything by no means makes an asynchronous operation synchronous. Did you event read the question?
Crescent Fresh
@megamatt: you're correct, there was an extraneous 'var' in there; @crescent: the original question wasn't asking anything about async, only the edits after I posted added specific async points.
johnvey
+1  A: 

"But this is done via subsequent interactions on the page, and I cannot foresee what the user will want to do with the JSON object within the callback."

The callback is your opportunity to set the screen up for the user's interaction with the data.

You can create or reveal HTML for the user, and set up more callbacks.

Most of the time, none of your code will be running. Programming an Ajax page is all about thinking about which events might happen when.

There's a reason it's "Ajax" and not "Sjax." There's a reason it's a pain to change from async to sync. It's expected you'll do the page async.

Event-driven programming can be frustrating at first.

I've done computationally intensive financial algorithms in JS. Even then, it's the same thing--you break it up into little parts, and the events are timeouts.

Animation in JavaScript is also event driven. In fact, the browser won't even show the movement unless your script relinquishes control repeatedly.

Nosredna
You've convinced me that sync ajax is not the way to go, but I'm still struggling with flow then. See my follow-up comment to Dave Ward's answer.
Mega Matt
+2  A: 

Remember that when you supply a callback function, the point of that is to defer the execution of that callback until later and immediately continue execution of whatever is next. This is necessary because of the single-threaded execution model of JavaScript in the browser. Forcing synchronous execution is possible, but it hangs the browser for the entire duration of the operation. In the case of something like $.getJSON, that is a prohibitively long time for the browser to stop responding.

In other words, you're trying to find a way to use this procedural paradigm:

var foo = {};

$.getJSON("url", function(data) {
  foo = data.property;
});

// Use foo here.

When you need to refactor your code so that it flows more like this:

$.getJSON("url", function(data) {
  // Do something with data.property here.
});

"Do something" could be a call to another function if you want to keep the callback function simple. The important part is that you're waiting until $.getJSON finishes before executing the code.

You could even use custom events so that the code you had placed after $.getJSON subscribes to an IssuesReceived event and you raise that event in the $.getJSON callback:

$(document).ready(function() {
  $(document).bind('IssuesReceived', IssuesReceived)

  $.getJSON("url", function(data) {
    $(document).trigger('IssuesReceived', data);
  });
});

function IssuesReceived(evt, data) {
  // Do something with data here.
}

Update:

Or, you could store the data globally and just use the custom event for notification that the data had been received and the global variable updated.

$(document).ready(function() {
  $(document).bind('IssuesReceived', IssuesReceived)

  $.getJSON("url", function(data) {
    // I prefer the window.data syntax so that it's obvious
    //  that the variable is global.
    window.data = data;

    $(document).trigger('IssuesReceived');
  });
});

function IssuesReceived(evt) {
  // Do something with window.data here.
  //  (e.g. create the drag 'n drop interface)
}

// Wired up as the "drop" callback handler on 
//  your drag 'n drop UI.
function OnDrop(evt) {
  // Modify window.data accordingly.
}

// Maybe wired up as the click handler for a
//  "Save changes" button.
function SaveChanges() {
  $.post("SaveUrl", window.data);
}

Update 2:

In response to this:

Does anyone have an idea how I should be blocking user interaction while this is going on? Why is it such a concern? Thanks again for all the responses.

The reason that you should avoid blocking the browser with synchronous AJAX calls is that a blocked JavaScript thread blocks everything else in the browser too, including other tabs and even other windows. That means no scrolling, no navigation, no nothing. For all intents and purposes, it appears as though the browser has crashed. As you can imagine, a page that behaves this way is a significant nuisance to its users.

Dave Ward
I never think to use custom events. I shall endeavor to do so. Great answer.
Nosredna
Thanks for the answer Dave. So I want run a scenario by you. Let's say, within the $.getJSON callback, I take the data that I get from the call, and load a jquery drag drop list that the user can then remove Issues from if he chooses. The user doesn't want a particular Issue assigned to him, and "drops" it. Then he wants to save that list that is assigned to him. Here I want to update that information, and a JSON object would be perfect. But I no longer have access to that info because the callback has finished executing. I don't have a list to remove an Issue from! How should I handle this?
Mega Matt
@Nosredna: Custom events are very underused, IMO. They're great for cleaning up the nested anonymous function spaghetti that inline event handlers tend to produce.
Dave Ward
Dave, I think your answer may violate my original issue with asynchronous calling. A global variable seems like a good solution, but only if I can ensure that it is called *after* the $.getJSON runs. If I need to be able to manipulate the data after the callback completes, it sounds like the only I can store that data is in a variable outside of the scope of the callback (and within the scope I need it). And the only way to ensure that the variable's values are set it to set ajax to sync. Good conclusion?
Mega Matt
@Matt: In that case, you should probably use a global variable (the window.foo that others have mentioned) to store the data in. You could still use a custom event to notify IssuesReceived that it should then build the drag 'n drop interface based on what's in the global variable though. As events like "drop" happen, you can modify the global object. Then, eventually send that up-to-date object back to the server to save the user's changes.
Dave Ward
Matt, just make sure you can handle the state where you haven't gotten the info yet. Check if the object is empty. Or change a global boolean to indicate the load has happened.
Nosredna
@Matt: Take a look at my recent edits, including the OnDrop and SaveChanges functions, and see if that makes sense. With event-driven code, the idea is to act on these events as necessary, not to orchestrate a certain chain of code to execute all in synchronous order. Anything else is going against the grain when you're working in the single-threaded browser environment; especially when using an event-driven framework like jQuery.
Dave Ward
@Dave: Really stellar answers. Thanks for the time input. I'm still running synchronously, but will use your suggestions to move off of this, as I'm entirely convinced that it's the wrong way to be headed. I'll need to get a better grip on order of execution here once I begin to implement your suggestions. I haven't read up much on triggers, but your code there appears to be doing the same thing as a simple function call when you say "$(document).trigger('IssuesReceived');". But I understand it's event driven.. I just need to read up on it some. Again. thanks for the detailed response.
Mega Matt
The custom event triggering is basically the same in this simple example, you're right. It does allow you to decouple the functions a bit though, which is nice. That way your AJAX callback doesn't need to drive the next action so directly (which you seemed to dislike), but just notifies interested event consumers that the data has been updated. As your client-side code gets more complex, this is a great way to manage that complexity.
Dave Ward