Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Variables set during $.getJSON function only accessible within function

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.

like image 970
MegaMatt Avatar asked Nov 16 '09 02:11

MegaMatt


2 Answers

$.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(); }); 
like image 189
strager Avatar answered Sep 22 '22 14:09

strager


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.

like image 32
Dave Ward Avatar answered Sep 23 '22 14:09

Dave Ward