2

What is the best to use global variables outside of a callback function?

    var icon; 
    $(function(){

      $.get('data.xml', function(xml){

           icon = xml.documentElement.getElementsByTagName("icon");
           //this outputs a value
           console.log(icon);
       });
       //this is null
       //How can this maintain the value set above?
       console.log(icon);
    });
Mike
  • 12,359
  • 17
  • 65
  • 86
  • What are you trying to archieve? – thejh Nov 29 '10 at 20:34
  • how can it maintain what value? you need to be more clear. – Ben Lee Nov 29 '10 at 20:34
  • there is no best way you just use them – stevebot Nov 29 '10 at 20:35
  • It just needs to hold the value of the xml element. – Mike Nov 29 '10 at 20:35
  • This question has been asked a million times. Since $.get is asynchronous, the function passed to it doesn't execute immediately. Therefore, when you call console.log after calling $.get, the callback has not been called yet. That's why addding a long enough timeout would work (but would be a really nasty hack) – Ruan Mendes Nov 29 '10 at 21:08
  • @Juan Thanks for letting us know that the question was asked so many times and that you can solve it with a hack. Why bother? – Mike Nov 30 '10 at 01:43
  • @Mike Is that sarcasm? I explained in the comment what the cause of the problem is. I'm giving you insight into your problem. – Ruan Mendes Dec 02 '10 at 21:41

4 Answers4

6

The code you have provided is perfectly valid -- and, in fact, icon does "maintain" it's value. The problem, likely, is that get() runs asynchronously -- only calling the anonymous function after 'data.xml' has been fully loaded from the server. So the real-world sequence of execution looks something like this:

  1. call get('data.xml', function(xml){...}) (starts loading data.xml)
  2. call console.log(icon) (icon is still null at this point)
  3. (data.xml finished loading) now the anonymous function is called, which assigns the value to icon: icon = xml.documentElement.getElementsByTagName("icon").

If you want to do something with the value of icon, after the 'data.xml' has been fetched, then you'll need to do it inside the anonymous callback function. Like this:

var icon; 
$(function(){

  $.get('data.xml', function(xml){
       icon = xml.documentElement.getElementsByTagName("icon");
       console.log(icon);
   });
});

good luck!


Note: you can still use icon from code that is outside the anonymous function, but you'll need to wait to access it, until after the anonymous function has been run. The best way to do this is to put the dependent code into its own function, and then call that function from within the callback function:

var icon; 
$(function(){

  $.get('data.xml', function(xml){
       icon = xml.documentElement.getElementsByTagName("icon");
       loadIcon();
   });

   function loadIcon() {
       console.log(icon);
       // ... do whatever you need to do with icon here
   }
});
Lee
  • 13,462
  • 1
  • 32
  • 45
  • I was trying to avoid your second solution. But it looks like this is the only way to go. To make matters worse I had to add a setTimeout function in order to make it work. AH! – Mike Nov 29 '10 at 20:59
  • 1
    @Mike, if you'll post a little more about what you're doing (or maybe open another question), we can help you avoid having to use a timer. (*please* let us help you avoid that -- the timer is almost certainly not required here). – Lee Nov 29 '10 at 21:03
  • @Mike: Your issues arise from google maps not being fully initialized at the point when you call your `addMarkers()` function. As shown in their [tutorial](http://code.google.com/apis/maps/documentation/javascript/tutorial.html), google expects the Map objects to be created in the window (or body) load events -- doing otherwise, will produce "strange behavior". Your use of jQuery `$(function(){...})`, binds your initialization to the DOM load event, which occurs before the window/body load events. Create a new S.O. question, (post a link here so I see it), and I'll help you sort through it. – Lee Nov 29 '10 at 21:28
  • @Mike: Check out the jQuery docs for [`ready()`](http://api.jquery.com/ready/). Also check out the [Events Section of the Google Maps v3 API docs](http://code.google.com/apis/maps/documentation/javascript/events.html) -- scroll to the bottom, it shows a google-provided way to hook the window-load event, and call your initialize function: `google.maps.event.addDomListener(window, 'load', initialize);` This approach *should* co-exist acceptably with jQuery's various event hooking mechanisms. – Lee Nov 29 '10 at 21:35
  • it seems to all work ok if I place the code inside of a body load event and get rid of the document ready function. Would be a good time to look into the doc ready vs body load vs window load. THANKS – Mike Nov 29 '10 at 21:47
  • @Mike: glad you got it sorted out. The main difference between the "Window Load Event" (or body.onload) and the "DOM Load Event" (eg. jQuery's `$(document).ready(...)`) is that the DOM event will fire as soon as all the html DOM elements are available for manipulation; whereas the window load event won't fire until *all* resources are loaded (this includes all images, scripts, css, etc. contained in the page). In most cases, "DOM Load" is what you want... google maps is an exception. It's possible to do both (but no need, if you've got it working as-is). cheers. – Lee Nov 29 '10 at 22:19
  • I take that back. I am pretty sure in this case this code requires a setTimeout() along with the second solution above. Gotta think there is a way, but for now setTimeout() will remain. – Mike Nov 30 '10 at 05:44
1

console.log(icon); won't have a value at that point as you're doing asynchronous ajax. Move your entire code that handles the response in the callback function or functions it calls.

$(function(){

  $.get('data.xml', function(xml) {
       var icon = xml.documentElement.getElementsByTagName("icon");
       console.log(icon);
   });
});
thejh
  • 44,854
  • 16
  • 96
  • 107
  • I need to use the variable outside of that callback function. – Mike Nov 29 '10 at 20:37
  • 1
    @Mike, you can't use it outside the callback function. You can restructure your code so that the callback function in turn calls a custom function you've written. Or you can put the custom code directly into the callback function. But there is no way to directly use it after the ajax call, because as everyone here has been saying it is asynchronous (it is possible to make the call synchronous but this is a very bad idea, because synchronous ajax can lock the browser). – Ben Lee Nov 29 '10 at 20:47
1

The problem is that $.get is queuing a request, but does not execute the request synchronously; it returns immediately. JavaScript is not multi-threaded!

You will have to execute console.log(icon) inside the callback function. At the point that line is being executed, the AJAX call has not completed yet.

The global icon variable will be set from the callback; your code is correct in that regard.

cdhowie
  • 158,093
  • 24
  • 286
  • 300
1

It can help to visualize the code like this.

    var icon; 
    $(function(){
        $.get('data.xml', callback); // sends ajax request
        // next line happens immediately unless ajax request is set to synchronous
        console.log(icon); // logs undefined
    });
    function callback(xml){ // onsuccess callback happens
        icon = xml.documentElement.getElementsByTagName("icon");
        console.log(icon); // logs Array
    }

I removed the anonymous function and placed the callback after the console.log. Like others have pointed out the ajax callback happens asynchronously, while javascript continues to execute.

Josiah Ruddell
  • 29,697
  • 8
  • 65
  • 67