Scoping Out the Javascript Function Scope Problem - javascript

It's very clear I don't understand how functions are scoped in Javascript. Here's the latest example:
function riseData() {
var jsonResult;
$.ajax({
success: function(data, textStatus, jqXHR){
jsonResult = jqXHR.responseText;
alert("Inside: " + jsonResult);
},
error: function (jqXHR, textStatus, errorThrown) {
$('#errLog').append('<br>Status: ' + qXHR.statusText);
}
});
return jsonResult;
}
$(document).ready(function(){
var intervalID = setInterval('UTCclock()',100);
alert("Outside: " + riseData());
});
When I execute this, the "Inside" alert functions properly, but the "Outside" alert displays "undefined", even though riseData() is obviously defined only a few lines earlier. There is a $.ajaxSetup earlier in the code which defines the parameters for the ajax call. The ajax call successfully returns the requested data in the "Inside" alert.
I just haven't the slightest clue how to make the necessary data (jqXHR.responseText) available to other parts of the script.
Can anyone point me at a "Javascript Scoping for Dummies" how-to that addresses this issue?

Your example is scoped just fine. Your problem is you don't understand the implications of how JavaScript is mainly asynchronous.
Let me go through your example to show you.
Your document ready handler runs.
It calls riseData.
riseData declares a variable, jsonResult.
riseData calls $.ajax, which schedules an AJAX request to be done, but the response will happen later.
Since $.ajax is usually non-blocking/asynchronous, riseData continues and returns jsonResult. Since jsonResult has not been assigned yet because the AJAX request has not completed, the default value of undefined is returned.
In the document ready handler, you end up with alert("Outside: "+undefined);.
An event loop to a few milliseconds later, this happens:
The AJAX request is completed. jQuery forwards this on to your callback.
jsonResult is set, but riseData has already returned.
The alert inside riseData alerts with the new data, after the document ready handler has already alerted undefined.
There are two solutions to this problem. The first solution is simple, but often results in a worse user experience. This solution is to add the async: false option to $.ajax. This makes $.ajax block and freeze the browser.
The second solution is to adopt an asynchronous style for almost everything. That means making riseData accept a callback and calling it inside the AJAX response handler. Here's your code transformed with this method:
function riseData(callback) {
$.ajax({
success: function(data, textStatus, jqXHR){
var jsonResult = jqXHR.responseText;
alert("Inside: " + jsonResult);
callback(jsonResult);
},
error: function (jqXHR, textStatus, errorThrown) {
$('#errLog').append('<br>Status: ' + qXHR.statusText);
}
});
}
$(document).ready(function(){
//var intervalID = setInterval('UTCclock()',100);
// Unrelated, but it's better to pass a
// function into setInterval than a string.
var intervalID = setInterval(UTCclock, 100);
riseData(function(jsonResult) {
alert("Outside: " + jsonResult);
});
});

This is only partially a scope issue. Your ajax call is asynchronous, so the riseData() function returns a value before the ajax call is executed. Try setting your variable outside the method.
var jsonResult;
function riseData() {
$.ajax({
...
jsonResult should not only be available in riseData(), but also out. However, you'll still have the issue of exactly when it gets populated.
A link for a little more explanation:
What is the scope of variables in JavaScript?

Related

Supply a non-anonymous function to Jquery: success + additional confusion

I can not get my JQUERY .ajax request to accept a function (non-anonymous) to the success condition.
The reason why I am choosing to not use an anonymous function (which I know will work) is because I think they are more difficult to debug and read. I also read that functions should be broken out as a good design and code-reuse factor.
$.ajax({
url: total_url,
type: "POST",
data: {already_voted: already_seen},
success: ajax_get_data_success(data),
error: function (XMLHttpRequest, textStatus, errorThrown) {
console.log('XHR ERROR ' + XMLHttpRequest.status);
}
My success function is NEVER sent the DATA object?
My error is Uncaught ReferenceError: data is not defined
Obviously data has not been defined, it is supposed to accept the DATA upon success.
Here is my function for the success case:
function ajax_get_data_success(data)
{
try{
console.log(data.id);
console.log("Recieved" + data.id);
update_seen_state(data.id);
update_ui();
}catch(err)
{
console.log("Ajax_get_data", err);
}
}
** Update: As suggested I removed the extra paramaters and the call worked. However, this has sent me through the rabbit hole further as to how calls are made in JQUERY.
**Per the JQUERY API, the success case takes only 3 paramater, and the error case takes 3 paramaters. From a programatic standpoint, that means when a successful ajax connection has been made, their code will pass to MY FUNCTION only 3 paramaters.
Wouldn't Javascript get confused if I increased the number of parameters that were allowed to be passed to the function?
The error is self explanatory. You are using the variable data which is not defined above. The javascript engine don't know what data is !
In your case for your ajax success event handler, just specify your function name, no need to specify the param there. Just have it in your function definition (which you already have).
This should work.
success: ajax_get_data_success,
EDIT : As per the comment
What if I wanted to pass it additional arguments?
You can do this
var myArray = ["My","Custom","Array","I want to pass"];
$.ajax({
url: total_url,
type: "POST",
data: {already_voted: already_seen},
success:function (ajaxResponse) { ajax_get_data_success(myArray , ajaxResponse) }
});
and now your method accepts 2 params
function ajax_get_data_success(yourData,ajaxResponseData)
{
console.log(yourData);
console.log(ajaxResponseData);
}
Well, now you are using again an an anonymous method in your success event.

ajaxSuccess event firing sequence

as a debugging/learning tool I insert this into my code:
$(document).ajaxSuccess(function() {
console.log('ajax complete');
});
I want to see where ajax gets its answer related to other events using console.log().
So I have:
function place_confirm (place_name,place_code) {
console.log('into place_confirm');
var place_confirm_html= '<p>confermi da '+place_name+ '?</p>';
//here the function does other stuff
}
$.post('./shyne/ajax/approve.php', {place_code:place_code}, function (data){
if (data == '_no_'){$('.error').html('il codice non è corretto');
} else {
place_confirm(data,place_code);
}
The strange thing to me is that the console.log writes:
into place confirm
ajax complete
while I thought it had to be the inverse: after ajax complete I have my function associated in success run.
So I have two questions:
does the console give a good representation of the sequence of events happening?
which is the right ajax event to attach a function I want to be executed only after Ajax got its response?
The function place_confirm will be triggered before the ajaxSuccess because the Ajax call is async. So while the JavaScrpt file is waiting for the Ajax Response it will perform other functions. That's why the function will be executed first.
If you want that the ajaxSuccess function will be executed first you need to set the async to false like below:
$.ajax({
async: false,
....
})
.done(function(response){
//Some Logic here if the Ajax succeded
})
.fail(function(jqXHR, textStatus, errorThrown){
alert('Error : ' + errorThrown); // error handling
});
But notice: If you set async to false Javascript MUST NOT perform other code until the AjaxResponse arrived! It will work like a singlethreated programming language!
Btw the code above is the new way to handle Ajax Calls correctly, the old way with Success und Error is deprecated in newer Versions, just for your information :)
The console can give you good information, but it only does so much. The console.log() can't give you information about WHY the functions are called in the order they are executed, but you'll see that they ARE executed in the order. The reason for that behaivior is explained in the first sentence, if you want further information just ask. :)
Sorry for possible bad grammar!
EDIT:
Welcome to stack overflow :)
From what I know the $.post function takes these parameters (URL, dataToPass, successFunction). So what you're doing is calling place_confirm function on success which will be called first before the other ajaxSuccess function you used. So to answer your questions:
1. Yes the sequence is correct.
2. You could use the done() event.
$.post('./shyne/ajax/approve.php', {place_code:place_code})
.done( function (data){
if (data == '_no_'){
$('.error').html('il codice non è corretto');
} else {
place_confirm(data,place_code);
}
})

How Do I return a javascript variable from a function that contains a $.get() request

I've tried Googling this but could not reslove it. It may seem like a really simple issue to others but I'm baffled by it. I have the below code in which I get undefined for the first alert but I still get the correct values in the 2nd alert. BUT if I comment out the first alert (just the line with alert) then the 2nd alert output becomes undefined. Can any one explain why this is and how I may output the 2nd alert correctly without the first one, any Help is greatly appreciated.
function getDetails(ID){
var qArray = [];
$.get('get_Question', {"Id":ID}, function(){})
.success(function(data){
var json = $.parseJSON(data);
qArray.push(json.value1);
qArray.push(json.value2);
});
//First Alert
alert("-> "+qArray[0]);
return qArray;
}
This is the 2nd alert which calls the above method:
var myArray = getDetails(4);
alert("myArray [0]: "+myArray[0]);
You can't return a value, the $.get() call is asynchronous.
You need to defer any operations on qArray until the AJAX call has completed, i.e. inside the callback.
Better yet, use deferred callbacks:
function getDetails(ID) {
return $.get('get_Question', {"Id":ID})
.pipe(function(json) {
return [json.value1, json.value2];
});
}
The .pipe deferred function creates a new promise which will ultimately return the desired array, but only once the AJAX call has completed.
You would then use this like this:
getDetails(ID).done(function(qArray) {
alert("-> " + qArray[0]);
});
Note that $.get() doesn't directly support error callbacks, but with deferred objects you can get access to them:
getDetails(ID).done(function(qArray) {
alert("-> " + qArray[0]);
}).fail(function(jqXHR, textStatus, errorThrown)) {
alert("The AJAX request failed:" + errorThrown);
});
Without this you'd need to build the error handling directly into the getDetails() function and then require some mechanism to tell the rest of the application logic about the error.
NB I've assumed that you don't really need to call JSON.parse() manually - if your web server returns the right Content-Type header then jQuery will do that for you automatically.
Ajax calls happens asynchroniusly, meaning you can't wait for the call to return and get the value. The way to do it is to employ a callback. Your example will become something similar to this:
function getDetails(ID, callback){
$.get('get_Question', {"Id":ID}, function(){})
.success(function(data){
var qArray = [];
var json = $.parseJSON(data);
qArray.push(json.value1);
qArray.push(json.value2);
callback(qArray)
});
}
Calling it will change a bit:
getDetails(4, function (myArray) {
alert("myArray [0]: "+myArray[0]);
});
The First Alert is called before the ajax call is finished, so the variable is still undefined.
This is because the $.get() is done asynchronously. There is no option for $.get() to pass parameter for async calls, so you should use $.ajax() instead and pass a param async: false
The $.get call creates a new asynchronous request for the resource in question.
When you call the first alert it is undefined because the request hasn't been completed yet. Also since you are forced to pause on the alert the request has time to be completed in the background. Enough time for it to be available by the second alert.
The same thing happens when you comment out the first alert. This time the second alert is called before the request is completed and the value is undefined.
You need to either make your requests synchronous or consider continuing execution after receiving the response by using a callback function within the success callback function you have already defined in $.get.
As several others have said, ajax-request are asynchronous. You could however set the async property to false to get a synchronous request.
Example:
function getDetails(ID) {
var result = $.ajax('get_Question', {
async : false,
data : { 'Id' : ID }
});
// do something with the result
return result;
}
I myself would have use a callback function instead beacuse async:false is bad practice and is also deprecated.
You'll need to rewrite $.get to use $.ajax and specify async: false
AJAX is asynchronous: you can't tell when the request will complete. This usually means you need to pass callback methods that will be called with the result of the request when it completes. In your case this would look something like:
function getDetails(ID, callbackFunc){
$.get('get_Question', {"Id":ID}, function(){})
.success(function(data){
var qArray = [];
var json = $.parseJSON(data);
qArray.push(json.value1);
qArray.push(json.value2);
callbackFunc(qarray);
});
}
getDetails(4, function(qArray) {
alert("myArray [0]: "+qArray[0]);
};

getJSON to string then loop through string

I have the following code which is included in a keypress function:
$.getJSON('dimensions.json', function(data) {
$.each(data, function(index) {
$('#div1').append(index);
});
});
I'm trying to first get the JSON string, save it in a variable and then run the each(). I want to basically separate the each() to be unlinked to the getJSON() function because I don't want it to fetch the json file for every keypress.
I've tried this, but it didn't work:
var JSONstr = $.getJSON('dimensions.json');
$.each(JSONstr, function(index) {
$('#div1').append(index);
});
In your first example, you do $.each in the callback. The callback is executed by some other callback after there result is received, while $.getJSON returns immediately without waiting for the result (since there is no blocking in JavaScript by design).
Therefore the code in your second example can never work: the $.each begins before any result is received from the web server, probably even before the request is sent. Whatever the return value of $.getJSON is, it can't, by the design of JavaScript, be the result of AJAX request.
UPD: Saw your comment, now I understand what you wanted to do. Here's a simple example of how to do this:
function ActualHandler(data) {
$.each(data, function(index) {
$('#div1').append(index);
});
}
function KeypressHandler() {
if (window.my_data) { // If we have the data saved, work with it
ActualHandler(window.my_data);
}
else { // Otherwise, send the request, wait for the answer, then do something
$.getJSON('dimensions.json', function(data) {
window.my_data = data; // Save the data
ActualHandler(data); // And *then* work on it
});
}
}
Here, the ActualHandler is not launched before the data is received, and once that happens, all subsequent clicks will be handled immediately.
The downside in this particular case is that if user clicks again while the first request is running, one more will be sent. But to fix that you would need to maintain some queue, which is kind of out of scope here.
You fell into the asynchronous trap. Your $.each() function doesn't wait for your $.getJSON() call to get the data. You can get around this by using the good 'ol $.ajax() function. Like this:
function processJSON(data) {
$.each(data, function(index) {
$('#div1').append(index);
});
}
$.ajax({
url: 'dimensions.json',
dataType: 'json',
async: false,
success: processJSON(data)
});

Trigger a function only after the completion of multiple AJAX requests

I've got a particular function I want to run once, and only after the completion of several AJAX requests.
My current solution looks a bit like this:
function doWork() {
//This is the function to be run once after all the requests
}
//some tracking/counting variables
var ajaxDoneCounter = 0;
var numOfAjaxRequests = 5;
var workDone = false;
function doWorkTrigger() {
ajaxDoneCounter++;
if( !workDone && ajaxDoneCounter >= numOfAjaxRequests ) {
workDone = true;
doWork();
}
}
// ...
//and a number of ajax requests (some hidden within functions, etc)
//they look something like this:
$.ajax({
url: "http://www.example.com",
dataType: "json",
success: function( data ) {
//load data in to variables, etc
doWorkTrigger();
}
});
One obvious pitfall in the above is that any AJAX call that is not successful will not increment ajaxDoneCount and so doWork() will probably never be called. I can get around that using the error callback in inside any $.ajax, so that doesn't worry me too much.
What I want to know is whether the above is safe and/or good practice?
Is there a trick I've missed, or any thing else that might work better?
Update: Since jQuery 1.5, deferred objects [docs] provide a cleaner solution. Have a look at an example here.
I would use .ajaxComplete(), it will be triggered whenever an Ajax call completed (success or error):
var numOfAjaxRequests = 5;
$(document).ajaxComplete(function() {
numOfAjaxRequests--;
if(!numOfAjaxRequests) {
doWork();
}
});
Then you don't have to edit every Ajax request.
You could even use .ajaxSend() to get notified of starting Ajax requests, instead of hardcoding it (but I am not sure whether this really works, maybe you will experience race conditions):
var numOfAjaxRequests = 0;
$(document).ajaxSend(function() {
numOfAjaxRequests++;
});
I think you should use complete(XMLHttpRequest, textStatus) ajax event instead of success(data, textStatus, XMLHttpRequest).
According to jQuery help:
complete(XMLHttpRequest, textStatus)
A function to be called when the
request finishes (after success and
error callbacks are executed). The
function gets passed two arguments:
The XMLHttpRequest object and a string
describing the status of the request.
This is an Ajax Event.
I don't know enough about JavaScript internals, but there is a danger that the operation:
ajaxDoneCounter++;
is not atomic. If that is the case, then this could be subject to a race condition.

Categories

Resources