I'm having a hard time grasping the concept of closures and variable scope in JS. Specifically, how do I access the deeply nested styleData variable in a class and then an object created from that class?
I'm sure I have a few other things wrong here, so please chime in and correct me where you see fit. Thanks!
var BuildJSON = {
convert: function() {
$.ajax({
type: "GET",
url: "style2.xml",
dataType: "xml",
success: function(xml) {
var styleData = $.xml2json(xml);
return styleData; // Do I need to return this somehow?
}
//How to get access to styleData??
});
},
styleData: this.convert();
};
var myClass = function() {
this.info = BuildJSON.styleData;
};
var myObject = new myClass;
alert(myObject.info.Style[0].name);
2 Answers 2
Closures in JavaScript are functions, so anything declared within a function scope will ONLY be visible inside that function.
In your example styleData is local, it belongs to the success function, and can't be accessed anywhere else. The easiest solution is to declare that variable at the top of the BuildJSON scope, in this case since you're declaring that object as an object literal you can initialize it as a property of that object:
this.styleData = '',
...
success: function(xml) {
BuildJSON.styleData = $.xml2json(xml);
}
The "problem" with this approach is that styleData is public, and maybe that's not what you want. In case you want to use that variable inside BuildJSON but not make it publicly accessible, the module pattern comes to the rescue.
var BuildJSON = (function(){
var styleData = '', // local
convert = function(){ ... } // You can use style data here
return {
convert: convert // Return only stuff you want to be public
}
}())
Comments
I would say that the big problem here is more about the asynchronous programming dus to the AJAX call then it is about callbacks per se.
One thing you could do is just set styleData explicitly from the ajax callback. Note how the "that" variable from the outer scope can be accessed and modified from the inner scopes.
var BuildJSON = {
convert: function() {
var that = this; // inner callbacks get separate "this"
// variables so we save the BuildJSON in a separate variable.
$.ajax({
type: "GET",
url: "style2.xml",
dataType: "xml",
success: function(xml) {
that.styleData = $.xml2json(xml);
}
});
}
};
BuildJSON.convert();
While this is simple to do, it has the downside that you are only allowed to read the "styleData" property after converts finishes running and the way you wrote the code you have no way to know that the ajax call has completed (other the polling the styleData variable with setinterval but that would be silly).
There are two main ways to "return" the inner value from the async function. One way is to do like $.ajax itself does, converting your function to continuation passing style. This way, instead of returning a styleData result you receive a function to call with the styledata when you are done calculating it
convert: function( onStyleData ) {
$.ajax({
// ...
success: function(xml) {
var styleData = $.xml2json(xml);
onStyleData( styledata ); // <---
}
});
};
BuildJSON.convert(function(styledata){
console.log('got styledata', styledata)
})
Another possibility is to take advantage of promise support in JQuery. Functions such as ajax return special promise objects that make async programming more convenient (since you can kind of write code returning values with "return" instead of being forced to do manual CPS.
I don't really know the names they use in JQuery for this, but in the Dojo toolkit it would look sort of like
var styleDataPromise = dojo.xhr({
url: /*...*/,
load: function(data){
return xmlToJSON(xml);
}
})
styleDataPromise.then(function(styleData){
console.log('got styledata', styleData)
})
myObjectandBuildJSONdeclared beforehand?varin front of them, like so:var BuildJSON = { ...andvar myObject = ...alert(myObject.info.Style[0].name);will always throw since the instance was created in this same call stack, and the Ajax response handler was not yet able to execute. If you want to use the style data from the Ajax response, you have to do it from within the Ajax response handler.