Change global variable inside JavaScript closure - javascript

I'm trying to change the value of a variable in a closure:
var myVariable;
$.ajax({
//stuff....
success:function(data) {
myVariable = data;
}
});
This does not work because myVariable is not visible to the closure. How do I change this code so that the value of myVariable changes?

Contrary to your belief, your code works. But seeing what you're trying to do and reading between the lines I'm guessing you're trying to do this:
var myVariable;
$.ajax({
//stuff....
success:function(data) {
myVariable = data;
}
});
alert(myVariable); // at this point myVariable is undefined!!
If so, you need to learn about asynchronous functions.
Basically, the $.ajax() function returns before actually submitting the ajax request. It will do the ajax request later when the browser is not busy executing javascript. Which means that the assignment will not have happened yet when you try to alert the value of myVariable.
Read my response here for more detail: JS global variable not being set on first iteration
The only good solution is to change the way you think about coding. (There is an arguably bad solution that involves turning the ajax call to synchronous but lets not get in to that, you can google it if you want or read the manual). Instead of doing this:
var myVariable;
$.ajax({
//stuff....
success:function(data) {
myVariable = data;
}
});
/*
* Do lots of stuff with the returned value
* of the myVariable variable
*
*/
you now need to write it like this:
var myVariable;
$.ajax({
//stuff....
success:function(data) {
myVariable = data;
/*
* Do lots of stuff with the returned value
* of the myVariable variable
*
*/
}
});
Basically moving any and all code that you would have written after the ajax call into the success callback. This takes getting used to (judging from how many variants of this question exist on the internet). But once you're used to it it becomes second nature.
There is a name for this style of programming. It is variously known as: "event driven programming" or "continuation-passing style" or "evented programming". You can google the various terms if you want to know more.

If that code is in the global scope, myVariable is visible to the inner function. If you're worried about it being shadowed by a local variable, explicitly access it as a property of the global:
var myVariable;
$.ajax({
//stuff....
success:function(data) {
window.myVariable = data;
}
});

Related

Variable scope within function

Read lots of posts lately, trying to demystify variable scopes in Javascript/SAPUI5. The way that I have understood things we have mainly 2 kinds of them - local variables, declared inside functions and visible only within and global variables declared outside functions and therefore "globally" accessible. Tried that with a really small example and, after what debugging revealed, I feel quite puzzled here. Tiny sample below:
onTestButtonPress: function() {
var url = "/DEV/sap/opu/odata/SAP/ZCONTRACTS_SRV/Agreement_ExportSet";
var oTest1 = [];
var promise = $.Deferred();
$.ajax({
url: url,
type: "GET",
dataType: "json",
success: function(xhrData) {
var oTest2 = xhrData;
promise.resolve();
}
});
var readyToGo = function() {
jQuery.sap.log.error("check");
};
jQuery.when(promise).done().then( jQuery.proxy(readyToGo, this) );
}
On $.ajax... and jQuery.when... statements, oTest1 var has a value of [] while within the functions contained in the main function (ajax success function & readyToGo), same variable is producing a reference error.
According to the "theory" and unless I miss something, since oTest1 was declared at the beginning of the main function, it should be visible within the two nested functions as well. What do I miss here or, to put it in a different way, where (or how maybe?) should I declare my variable in the function in order to be visible?
Thanks in advance,
Greg
UPDATE: After a (fair) suggestion, modified the code and attached code for the variable in doubt. New code is this:
onTestButtonPress: function() {
var url = "/DEV/sap/opu/odata/SAP/ZCONTRACTS_SRV/Agreement_ExportSet";
var oTest1 = [];
var promise = $.Deferred();
$.ajax({
url: url,
type: "GET",
dataType: "json",
success: function(xhrData) {
oTest1 = xhrData;
promise.resolve();
}
});
var readyToGo = function() {
jQuery.sap.log.error(oTest1);
};
jQuery.when(promise).done().then( jQuery.proxy(readyToGo, this) );
}
This code is running just fine and variable is OK. Still running the old code, debugger produces this:
This is the reason that I didn't use the var in the sample, is this normal?
You get an error in the code in the screenshot because oTest1 is not in scope.
Scope is determined when the function is created, and since the oTest1 variable is not used in the function then it isn't in scope.
You attempt to add it to the scope after the function has been created by reading the variable with the debugger. It is too late.
Add console.log(oTest1) before debugger and it will be in scope.

How can I wrap code into a module in order to avoid using global variables?

After my previous question, I come up to the following working code that is intended to refresh the DOM periodically by replacing the <div id="test_css_id">...</div> itself. The behavior of both AJAX requests present in the below code is to reload the same code itself.
<div id="test_css_id">
<a id="link_css_id" href="test_url.html">LINK</a>
<script type="text/javascript">
var refreshTimer;
$('#link_css_id').click(function(event) {
event.preventDefault();
$.ajax({
url: $(this).attr('href'),
type: 'PUT',
success: function(data) {
clearInterval(refreshTimer);
$('#test_css_id').replaceWith(data); // Replaces all code including JavaScript with the response data (note: the response data is exactly the same as the code shown here).
}
});
});
$(document).ready(function() {
function refreshFunction(){
$.ajax({
url: 'test_url.html',
type: 'GET',
success: function(data) {
clearInterval(refreshTimer);
$('#test_css_id').replaceWith(data); // Replaces all code including JavaScript with the response data (note: the response data is exactly the same as the code shown here).
}
});
}
refreshTimer = setInterval(refreshFunction, 1000);
});
</script>
</div>
However, as said by the author of the accepted answer, "there are other ways you can do it [..] one way is to wrap all of that code into a module". I am not expert in JavaScript but I would like to understand and learn it a little more.
How can I wrap all of that code into a module in order to avoid using global variables?
Your current code looks like this:
var refreshTimer; //a global variable
$(...).click(...);
To make refreshTimer not global, you need to put it inside a function:
function main(){
var refresherTimer; //this variable is now local to "main"
$(...).click(...);
}
main();
However, doing it this way won't solve the problem completely. While we did get rid of the global variables, we added a new one - the "main" function itself.
The final trick is to turn the "main" function into an anonymous function and invoke it directly. This is the famous "module pattern":
(function(){
var refreshTimer; //local variable to that anonymous function
$(...).click(...);
}()); //and here we call the function to run the code inside it.
The extra parenthesis around everything are important. If you do just function(){}() instead of (function(){}()) then you will get a syntax error.
Here's a nice description of the module pattern in JavaScript.

Make AJAX "get" function synchronous / how to get the result?

I'm experiencing a problem of $.get function.
The url contains JSON
my code:
xyz = null
$.get('http://www.someurl.com/123=json', function(data) {
var xyz = data.positions[0].latitude;
});
alert(xyz);
//some more code using xyz variable
I know that xyz will alert a null result because the $.get is asynchronous.
So is there any way I can use the xyz outside this get function?
get is a shortcut. You can do the same, but synchronous, using:
var xyz = null
$.ajax({ url: 'http://www.someurl.com/123=json',
async: false,
dataType: 'json',
success: function(data) {
xyz = data.positions[0].latitude;
}
});
alert(xyz);
You'll have to declare the xyz variable before the ajax call, though.
The real answer is NO, but you can use this:
function useXYZ(){
alert(xyz);
}
xyz = null
$.get('http://www.someurl.com/123=json', function(data) {
xyz = data.positions[0].latitude;
useXYZ();
});
This is a common issue with Javascript. Javascript code must be written in continuation passing style. Its annoying but its something you can convert without thinking too much.
Basicaly, whenever we would have something like
var x = someSyncFunction(a, b, c);
//do something with x
console.log(x);
We can convert it into async code by making all the code after the function returns into a continuation function and turning x from a variable into a parameter of the continuation callback.
someAsyncFunction(a, b, c, function(x){
//do something with x;
console.log(x);
});
You have to watch out that its very easy to write confusing code. A good trick to keep in mind is taht you can make your own functions also receive callbacks. This allows them to be used by different function (just like normal sync helper functions that return a value can be used by different functions)
var getXyz = function(onResult){ //async functions that return do so via callbacks
//you can also another callback for errors (kind of analogous to throw)
$.get('http://www.someurl.com/123=json', function(data) {
var xyz = data.positions[0].latitude;
onResult(xyz); //instead of writing "return xyz", we pass x to the callback explicitely.
});
};
getXyz(function(xyz){ //this would look like "var xyz = getXyz();" if it were sync code instead.
console.log('got xyz');
});
The trick here is to change all return statements from the function into calls to the callback function. Think as if async function never returned and the only way to give a value back to someone is to pass that value to a callback.
You might ask why there isnt an easier way to do all of this. Well, there is not, unless you use another language instead of Javascript (or at least something that lets you write async code in synchronous style but automatically compiles down to regular Javascript)

AJAX load in conjunction with Getters and Setters

I've been working with getters and setters to avoid the prospect of using global variables. However, I've run into a problem. The below code, which works fine with integer variables, throws an exception when I try to run an AJAX call instead. Can someone explain to me why this is happening?
function Object_XML() {
me = this;
me.xml = null;
}
Object_XML.prototype = {
getXML: function() {
return me.xml
},
setXML: function(data) {
me.xml = data;
},
loadXML: function() {
$.ajax({
type: "GET",
url: "questions.xml",
dataType: "xml",
success: function(xml) {
me.setXML(xml);
} //close success
});//close AJAX
}//close setXML
};
$(document).ready(function() {
var data = new Object_XML();
alert("This is an " + data.getXML());
data.setXML();
alert("This is an " + data.getXML());
});
Thanks, Elliot Bonneville
You just negated your use of private variables with getters and setters by using me = this; You just made me a global variable by not using var. (any variable not defined using var gets attached to the global namespace)
In your case since you're working within the same object scope you can just use this and avoid the me as personally, i think it's confusing. But if you want to stick to that paradigm, use var me = this;
Your example is really unclear, where does the error happen? You're calling data.setXml() with no parameters, so me.xml will bet set to undefined. That is to be expected if you pass nothing into the method.
Also keep in mind that due to the async nature of your call, if you were to do something like:
data.loadXml();
console.log("data.getXML();", data.getXML()); // would be undefined
data.getXML() at that moment would still be undefined as it's likely your asynchronous call hasn't returned yet, thus not setting the xml attribute of your object.

Using a closure to execute a function in the future?

I'm trying to use a closure (I think that's what it is..), I'd just like to execute a function with a local variable at some point in the future, like this:
function boo() {
var message = 'hello!';
var grok = function() { alert(message); }
foo(grok);
}
function foo(myClosure) {
$.ajax({
timeout: 8000,
success: function(json) {
myClosure();
}
}
}
I could get around this by using global variables and such, but would rather use something like the above because it at least seems a bit cleaner. How (if possible) do you do this?
Thanks
----------- Update --------------------
Sorry I wasn't clear - I was wondering if this is the correct syntax for the closure, I tried it out and it seems ok. Thank you.
Your existing code looks perfectly fine except for that missing paren at the end. ;)
If you're looking to understand the concept of closures more deeply, think of it this way: whenever something in a closured language is defined, it maintains a reference to the local scope in which it was defined.
In the case of your code, the parameter to $.ajax() is a newly-created object ("{ timeout: 8000, etc. }"), which contains a newly-created function (the anonymous "success" function), which contains a reference to a local variable ("myClosure") in the same scope. When the "success" function finally runs, it will use that reference to the local scope to get at "myClosure", even if "foo()" ran a long time ago. The downside to this is that you can end up with a lot of unfreeable data tied up in closures -- the data won't be freed until all references to it have been removed.
In retrospect, I may have confused you more than helped you. Sorry if that's the case. :\
Unless you actually want to make an AJAX call, setTimeout might be more along the lines of what you are looking for:
function foo(myClosure) {
setTimeout(myClosure, 8000); // execute the supplied function after 8 seconds
}
If your question was more along the lines of "Am I creating a closure correctly?", then yes, your function boo is doing the right thing.
Is it what you want?
var boo = (function() {
var message = 'hello!';
return function() {
foo(function() {
alert(message);
});
};
})();
function foo(myClosure) {
$.ajax({
timeout: 8000,
success: function(json) {
myClosure();
}
}
}
or just
function boo() {
$.ajax({
timeout: 8000,
success: function(json) {
alert('hello!');
// do sth with json
// ...
}
}); // <- missed a paren
}
The example is too simple to know what you want btw.

Categories