JavaScript scoping rules possibly being tampered with by jQuery/Ajax? - javascript

Note: I will specify the workaround I found for this issue, but I still do not understand why the first method did not/does not work for me.
I have a jQuery script for my HTML file that is supposed to send a number to a server via an ajax request. Here is the way my script is laid out:
// Beginning of script
var number = 0; //ensure that the variable has global scope
// Send number to server on click of button (which has type 'button' not 'submit') in HTML document
$(document).ready(function() {
$( "#button" ).click(function() {
number = 0; // reset the value of the number every time the button is clicked
// A function runs here and returns a number, and it has a callback (which gets passed that returned number).
// Callback is defined as follows:
function callback(returnedNumber) {
if (condition == true) {
alert("Condition passes");
} else {
// assign returnedNumber to 'number', then initiate ajax POST request to server
number = returnedNumber; // just assume returnedNumber is 23
}
// Notice that the ajax request is NOT initiated as part of the else statement.
$.post("test.php", { key: number }, function(data) {
$( "#resultDiv" ).html(data);
});
}
});
});
Now here is what the "test.php" file on the server looks like:
<?php
echo var_dump($_POST);
?>
When the condition in the callback does not pass, var_dump() shows that the value of $_POST["key"] is still 0 instead of 23, and this is where I get confused. My understanding of JS scoping rules is that once a variable is declared globally, functions can modify its value as long as the var keyword is not used to re-declare the variable within the function. I thought this would mean that using my callback to reassign the value of number would also change the value of the global variable number, thus allowing me to send it to the server without the ajax request being a part of the else statement that reassigned the variable. So, what part of that do I have wrong? If there is documentation that will help clarify my misunderstanding, please provide a link. :)
My Workaround: I simply appended the ajax POST request to the else statement, and that worked as I wanted it to. But I do not understand why the ajax request does not take the updated value of number when the request is not part of the else statement.
Thanks!

As you can see in this example, you are right about the scope part, so there must be something else going wrong, probably in the callback. Perhaps you're trying to get the number in an async-way, posting the data before you've gotten a response?
var condition = false;
var number = 0; //ensure that the variable has global scope
$("#button").click(function() {
number = 0; // reset the value of the number every time the button is clicked
function callback(returnedNumber) {
if (condition == true) {
//alert("Condition passes");
} else {
// assign returnedNumber to 'number', then initiate ajax POST request to server
number = 23; // just assume returnedNumber is 23
}
condition = !condition;
$("#result").text(`Result: ${number}. Setting condition to ${condition}`);
}
// Call the callback as an actual callback
setTimeout(callback, 1000);
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<button id="button">Click...</button>
<div id="result"></div>

Related

Why the variable value from is not accessible in following code snippet?

I've following Javascript code snippet :
authData=ref.getAuth();
if(authData == null){
//TODO find an elegant way to manage authorization
// window.location = "../index.html";
} else {
ref.child("users").child(authData.uid).on("value", function(snapshot){
$( "span.user-name").html(snapshot.val().displayName);
loggedInUser.displayName = snapshot.val().displayName;
//alert("Name inside : "+loggedInUser.displayName);
//Here it displays the value
});
}
alert("Nameada is out : "+loggedInUser.displayName);
//Here it shows 'undefined'
why?
I want to use the variable value loggedInUser.displayName where did I shown alert.
Can someone please help me in accessing the value and displaying the alert?
Thanks.
Your final alert is executed when the callback function (function(snapshot){ ... }) has not yet been called. Note that the callback function is called asynchronously, so it only gets executed after the currently running code has completed and the value event is triggered.
This also explains why the inner (commented out) alert does work. Just realise that this piece of code (the call back function) is executed later than the other alert, even though it occurs earlier in your code.
You could "solve" it by calling another function from within the call back, like this:
authData=ref.getAuth();
if(authData == null){
//TODO find an elegant way to manage authorization
// window.location = "../index.html";
} else {
ref.child("users").child(authData.uid).on("value", function(snapshot){
$( "span.user-name").html(snapshot.val().displayName);
loggedInUser.displayName = snapshot.val().displayName;
whenUserLogged();
});
}
function whenUserLogged() {
alert("Name : "+loggedInUser.displayName);
// anything else you want to do....
}
Some suggestions for improvement
Don't use too many global variables (in your code all of them are global), and instead pass variables as function arguments.
You may want to look into promises.

jQuery function execution order

I am having a problem, or perhaps a lack of understanding, with the jQuery execution order of $.get() function. I want to retrieve some information from a database server to use in the $.ready() function. As you all know, when the get returns, it passes the data to a return handler that does something with the data. In my case I want to assign some values to variables declared inside the ready handler function. But the problem is, the return handler of $.get() does not execute until after ready has exited. I was wondering if (a) am I doing this right/is there a better way or if (b) there was a way around this (that is, force the get return handler to execute immediately or some other fix I'm not aware of). I have a feeling this is some closure thing that I'm not getting about JavaScript.
As per request, I'll post an example of what I mean:
$(function() {
var userID;
$.get(uri, function(returnData) {
var parsedData = JSON.parse(returnData);
userID = parsedData.userID;
});
});
So as you can see, I'm declaring a variable in ready. Then using a get call to the database to retrieve the data needed. Then I parse the JSON that is returned and assign the userID to the variable declared before. I've tested it with a couple alerts. An alert after the get shows userID as undefined but then an alert in get's return handler shows it to be assigned.
$.get() is asynchronous. You have to use a callback to fill your variable and do the computation after the request is complete. Something like:
$(document).ready(function(){
$.get( "yourUrl", function( data, textStatus, jqXHR ) {
var myData = data; // data contains the response content
// perform your processing here...
registerHandlers( myData ); // you can only pass "data" off course...
});
});
// your function to register the handlers as you said you need to.
function registerHandlers( data ) {
// registering handlers...
}
$.get is an ajax request. A in AJAX stand for asynchronous, so script won't wait for this request to finish, but instead will proceed further with your code.
You can either use complete callback or you can use $.ajax and set async to false to perform synchronous request.
The $.get() function executes an async httprequest, so the callback function will be executed whenever this request returns something. You should handle this callback outside of $.ready()
Maybe if you explain exactly what do you want to do, it would be easier to help!
Are you looking for something like:
$(document).ready(function(){
var variable1, variable 2;
$.get('mydata.url', function(data){
variable1 = data.mydata1;
variable2 = data.mydata2;
});
});
If you declare the variables first, then you can set their values within the get call. You can add a function call at the end of the get handler to call a separate function using these values? Without some kind of example, its hard to go into any more detail.
Without seeing the full code, my guess is that you should declare your variable outside $.ready; initialize it in ready for the initial page load; then update it from the get callback handler.
for example
var x = ""; // declaration
$(document).ready(function() { x = "initial value"; });
$.get(...).success(function() { x = "updated from ajax"; });

Variable scope issue on returning value from $.post

I have one of those pesky variable scope problems with $.post. Before submitting a form, I want to first check if the user has edited the content. I return false if the user hasn't.
I've looked at a bunch of different examples on Stackoverflow and the general way to do this is to create a function and a callback function inside that function to handle the callback. My problem is that the when I call getReturned below, the callback does not get implemented (know this because form gets submitted and I've tried an alert). Btw, alert("No changes were made so text was not submitted."); gets called successfully.
What am I doing wrong?
function getReturn(callback, id, old_variable){
var return_value = "ERROR";
$.post('myURL', {"id" : id},
function(data) {
var text_from_server = $.trim(data[0].note_text);
if (old_variable == text_from_server){
alert("No changes were made so text was not submitted.");
return_value = false;
callback(return_value);
} else {
return_value = true;
callback(return_value);
}
},
"json"
);
};
var id = 123;
var old_variable = "foo";
getReturn(
function(return_value){return return_value;/*alert("SUCCESS!");*/},
id,
old_variable
);
Since you are making an asynchronous request to test if the value has changed you have to always return false from your submit handler. In the callback, if the form was edited, you would then manually submit the form using form.submit(). Something like this:
getReturn(
function(return_value) {
if (return_value) {
$("#myForm").submit();
}
},
id,
old_variable);
return false;
You don't understand asynchronous execution. You can not return anything from $post! Do not forget that. All you can do is trigger execution of something - you can try to set a variable flag, but it won't help you. You also need to trigger the execution of a function that does something with that flag, and you have to call that function from inside $post.
The function you are sending in as the callback returns a value. But it does not return the value to anyplace. You are calling it inside $post, and it's returning the value inside $post - which does nothing useful for you at all since you aren't doing anything with the return value.
I would tell you how to change it, except you have not said what you want to do with this return value.

Javascript call a function and do not call it again until it has returned

I have a button that when clicked calls a function - this function does some asynchronous Ajax and alerts a messagebox when the Ajax has returned. I do not want the user clicking on the button multiple times - if he clicks on the button when the Ajax has not returned then an error message should be alerted.
I know that this can be easily done using a global boolean variable (set it initally to true, make the ajax call and set it to false - set it again to true when the ajax returnes - check if the global is false when the user clicks the button). Also it can be done similarly if instead of the window/global object I use another global object containing the function and the boolean
However, I do not like very much the above methods - I think that they are a little old-school-Javascript. I was wondering if there was a more elegant way to do it, for instance using JS closures !
Using this method, your variable will not leak to the global scope. There's no way to manipulate this variable from outside the function:
var foo = (function(){
var pending = false;
return function foo(){
if(pending) return;
pending = true;
//...Code
//When finished:
pending = false;
}
})()
Others may suggest setting a property of the function, but this property can easily be adjusted from outside, which is not desirable.
var callback = (function()
{
var executing = false;
function yourfunc()
{
if(executing) return; // exit if pending
executing = true;
// here send the request
// edit: when the ajax response has returned
xxxx.onreadystatechange....{
// do what you need to do with ajax data
executing = false;
};
}
return yourfunc;
})();
callback();
Anonymous function has the form
(function(){}))()
The last () provides the parameters for the anonymous function.
In the above sample script ( by wes ) returns error as callback is not a defined function. Rob's method using closure sounds good.
Cheers.. Sree

XmlHttpRequest returning state 4 too soon

I'm developing a javascript code to run on an embedded device using the ANT Galio browser.
Ideally, I'd like the code to make a get request to another server. After that get request is made, the page would not allow the user to submit another get request, until a response had been received from the previous get request.
For some reason sometimes I am receiving a readyState of 4 almost instantly. It is as though it is evaluating the previous XmlHttpRequest object and not the new one. What am I doing wrong?
<script type="text/javascript">
var fail= function (env, resp, stat) {
alert(resp);
};
var succ= function (env, resp) {
};
var canScan = true;
/* start scan */
function scan (name) {
if (canScan) {
//deactivate button
deactivateScanButtons();
//let server know
ajax = new XMLHttpRequest();
var scanUrl = 'http://19X.1XX.X.XX:8080/scan/' + name
ajax.open('GET', scanUrl, true);
ajax.onreadystatechange = function() {
if (ajax.readyState==4) {
//allow button to work again
activateScanButtons();
alert("ready state 4");
};
};
ajax.send();
//initiate scan
xrxScanInitiateScan(
'http://127.0.0.1',
"ftp.xst",
false,
succ,
fail);
}
}
function deactivateScanButtons () {
// canScan = false;
var indicator = document.getElementById('buttons');
indicator.style.visibility = "hidden";
}
function activateScanButtons () {
// canScan = true;
var indicator = document.getElementById('buttons');
indicator.style.visibility = "visible";
}
</script>
3 suggestions:
To avoid any caching on the client side, add a randomly generated number, or the current timestamp to the request querystring.
As Yoni said, initiate your XMLHttpRequest object with var keyword.
For each request, save the current timestamp within a global variable. In onreadystatechange, call activateScanButtons only if the global timestamp matches the corresponding timestamp of that given request. This way, only the latest request will be able to call activateScanButtons.
You define the ajax object in scan function without the var keyword before it. This means that it is a global object, not local. Afterwards, you have a closure - you refer to that variable in the onreadystate callback function.
I find it hard to track exactly what's going on there, but I agree with you, as you say in your question, that the callback is not using the ajax object the way you expect. You say it happens sometimes - does it happen when you make two requests almost simultaneously (double-click a button or otherwise trigger the two get requests very fast)?
I suggest that you use the var keyword before defining the ajax object. Even better, try using this in the callback function instead of referring to the ajax object by name. If it works, you have spared yourself of one closure.

Categories