Lifetime of a JS variable with jQuery and AJAX - javascript

I'm currently trying to get around the async behavior of AJAX. Problem is, that I have an unspecified amount of AJAX calls that I all have to wait for. I'm creating with jQuery a deferred object that gets resolved manually as soon as the last ajax call has finished including its success-handler. works fine, but: it seems that the function, where all that happens, has terminated (and cleaned up all variables that were declared inside that function) before the then() function executes. I can only solve this problem by declaring the needed variable users globally.
If I declare
$().click(function() {
/* inside here */
var users = [];
});
then it doesn't work. Console states that the var users is not declared. (See code example).
What is a clean approach to solve this problem? Declaring all needed variables globally seems not really nice to me.
Link to jsfiddle with my code example

You will need to declare the variable in a scope where all functions can access it. As your getGroupMembers function is in the global scope, so your users variable needs to be. Move it into the ready or click scope, and you can declare the variable as local.
However, it would be much easier to pass the result of your request as arguments to resolve:
$(document).ready(function() {
$('#message_send').click(function() {
var deferred = getGroupMembers({
page: 1,
per_page: 100
});
deferred.then(function(users) {
console.log(users);
});
});
});
function getGroupMembers(querydata) {
var users = [];
var deferredObject = new $.Deferred();
…
// some asynchronous tasks, callbacking either
deferredObject.resolve(users);
// or
deferredObject.reject();
…
return deferredObject.promise();
}
For some syntactic sugar, you might as well just use the pipe method of the Ajax Deferred.
Recursive piped method:
function getGroupMembers(querydata) {
return $.ajax({
url: "/echo/json/",
dataType: "json",
data: querydata,
cache: false
}).pipe(function(data) {
var user = data.response.UserActions,
users = [];
for (var u = 0; u < user.length; u++) {
users.push(user[u].user.id);
}
if (querydata.page < data.meta.total_pages) {
querydata.page++;
return getGroupMembers(querydata).pipe(function(nextusers) {
return users.concat(nextusers);
});
} else {
return users;
}
});
}

You can get around this with closures but I would suggest looking at async.js
If you want to pass the last response of an ajax call to the next function
https://github.com/caolan/async#series
Probably a better choice if you are running one ajax after an another is waterfall
https://github.com/caolan/async#waterfall
waterfall is the same as series but stops if any of the functions fail / series doesn't
In the event of running multiple ajax calls and just waiting until they are all finished
https://github.com/caolan/async#parallel

Related

Javascript doesn't update variables written with PHP

I have a javascript function that calls an AJAX, like this:
function addSquadronByID(id) {
$.ajax
({
type: "POST",
url: "server/get_squadron.php",
data: {
'id': id,
'counter': squadron_counter
},
cache: false,
success: function (data) {
squadron_counter++;
},
error: function () {
alert("AJAX error.");
}
});
}
}
Outside the document.ready, the variable is initialized like this var squadron_counter = 0;
This function perfectly works while I call it in the page, but if I try to use PHP to write it in the page, like this:
$list_squadrons = $DB->Execute($query);
while(!$list_squadrons->EOF){
$currentSquadron_id = $list_squadrons->fields["squadron_id"];
$currentSquadron_number = $list_squadrons->fields["squadrons"];
echo "addSquadronByID($currentSquadron_id);\n";
$list_squadrons->MoveNext();
}
The function writes into the document.ready() the correct calls, but squadron_counter is always zero, even if the function works. My only idea is that it works this way because javascript calls all the functions at once and does not wait to complete the first one before executing the second one, etc.. but how do I solve this?
HTML output as requested:
addSquadronByID(3, squadron_counter);
addSquadronByID(5, squadron_counter);
addSquadronByID(6, squadron_counter);
This is put into a
$( document ).ready(function() {
});
inside a <script> tag.
I think your idea about JS calling all functions without waiting for the first one to complete is in the right direction. This is called "asynchronous requests". Please refer to How to return the response from an asynchronous call? for a detailed explanation.
The idea is to send your 3 requests and then wait for all of them to complete before checking the value of your squadron_counter variable (or whatever data you have updated in your success callbacks).
Then if my understanding is correct, you do not know how to implement this waiting?
Since you are using jQuery, the implementation is super simple. Note first that your jQuery.ajax request returns a Deferred object. So simply keep a reference of the Deferred object created by each AJAX request you send. Then you could use for example jQuery.when with a callback in its then method to wait for all your requests to complete:
function addSquadronByID(id) {
return jQuery.ajax({ /* ... */ }); // returns a Deferred object.
}
var d1 = addSquadronByID(3),
d2 = addSquadronByID(5),
d3 = addSquadronByID(6);
jQuery.when(d1, d2, d3).then(
// callback executed on success of all passed Deferred objects.
function () {
console.log(squadron_counter);
}
);
Demo: http://jsfiddle.net/btjq7wuf/

Elegant solution to conditional AJAX call

I am writing some javascript that includes a series AJAX calls and I am looking for an elegant solution to the following issue: The goal of the script is to gather parameters and then execute an API call with these parameters. The very first time the call is executed there is one parameter that needs to be requested from the server - every subsequent call will use a stored value of this parameter. This is where the issue begins. I want a conditional AJAX call to be made only if this is the first time. I don't want to put the rest of the code into the success function of that AJAX call as that seems convoluted. I would like something like the following but due to the obvious asynchronous nature of the call I realize this is not possible. I also want to avoid having a synchronous call as this would cause the thread to block:
var myParameter;
if(!params.myParam.isStored) {
myParameter = getParamWithAjaxCall();
} else {
myParameter = params.myParam;
}
// Continue with the rest of execution here of which there is a lot of code
Sorry if this seems like an obvious question and I have looked into solutions using the following but I am looking for an experienced opinion on what the most elegant solution would be:
jQuery: when.done
jQuery: async: false
Passing a callback to the Ajax call
I would create a wrapper function which you pass your logic to as a callback in done(). Something like this:
function makeRequest(callback) {
if (!params.myParam) {
// retrieve param
$.ajax({
url: '/getParam',
success: function(data) {
params.myParam = data.param;
}
}).done(callback);
}
else {
// param already has a value...
callback();
}
}
makeRequest(function() {
// make your AJAX request here, knowing that params.myParam will have a value.
});
You could use promises like so (I have used JQuery promises here):
function ParameterValueProvider() {
var parameterValue;
return function() {
var deferred = $.Deferred();
if ( parameterValue === undefined ) {
$.ajax({
// ... ajax parameters go here
}).done(function(rsp) {
parameterValue = rsp;
deferred.resolve(parameterValue);
});
}
deferred.resolve(parameterValue);
return deferred;
}
}
// Your Application
(function() {
'use strict';
var getParam = ParameterValueProvider();
// this will get the value from server the firs time
// and subsequent calls will use the cached value
getParam().then(function() {
// subsequent ajax calls go here
});
}());

is there a way to pause a script until a javascript closure is finished? JavaScript, jQuery, AJAX

If I use a closure to define something is there a means of waiting so to speak until the variable is populated before moving on to the next bit.
Example:
var myVari = someFunction();
$.each(myVari, function(){/*code for each*/});
the function that defines myVari is an AJAX call, which can take a second or 4 (yea its not to fast) to define the variable. Problem is, before the AJAX call yields its results the $.each has already fired off and errored due to myVari being empty. Is there a better way to approach this scenario?
You should adapt your code so that you can pass a callback to someFunction, which you execute when the AJAX call is completed.
The only way you can wait for the AJAX call to complete is to change the call to synchronous, but this is heavily discouraged as it locks up the browser completely for the duration of the AJAX call.
Because you are already using the jQuery libary, this process of callbacks becomes a whole lot easier. Instead of returning the variable like you are at the moment, I'd return the jQuery AJAX object (which has a promise interface as of 1.6), so you can easily add callbacks to it:
function someFunction () {
return jQuery.ajax('some/url.php', {
// whatever
});
}
var myVari = someFunction();
myVari.done(function (data) {
$.each(data, function(){/*code for each*/});
});
If I understand what you are trying to do, then you could try your $.each inside the 'success' handler of your ajax call.
Rewrite someFunction to something like -
var myVari; //define this here or in whichever calling scope where it needs to be available.
$.ajax({
'url': 'http://..',
'type': 'GET', // or POST
'data': { } // whatever data you need to send
'success': function(data) {
myVari = process_the_server(data);
$.each(myVari, function() {...});
}
});
Use a callback, like this:
someFunction(function(myVari) {
$.each(myVari, function(){ /*code for each*/ });
});
Then redefine someFunction like this:
function someFunction(callback) {
var myVari;
/* ... */
/* calcuate myVari */
/* ... */
/* instead of returning it, pass it to the callback: */
callback(myVari);
}
The correct way is: Instead of running the each on its own, run it inside the ajax call.
You could, I suppose do:
function checkFunc() {
setTimeout(function() {
if(myVari) {
$.each(........);
} else {
checkFunc();
}
}, 1000);
}
That not really good coding practice, but it will work.

Better way to detect when a variable != undefined when async request is sent

Given the following:
var doThings = (function ($, window, document) {
var someScopedVariable = undefined,
methods,
_status;
methods = {
init: function () {
_status.getStatus.call(this);
// Do something with the 'someScopedVariable'
}
};
// Local method
_status = {
getStatus: function () {
// Runs a webservice call to populate the 'someScopedVariable'
if (someScopedVariable === undefined) {
_status.setStatus.call(this);
}
return someScopedVariable;
},
setStatus: function () {
$.ajax({
url: "someWebservice",
success: function(results){
someScopedVariable = results;
}
});
}
};
return methods;
} (jQuery, window, document));
The issue is clear, this is an async situation were I would like to wait until someScopedVariable is not undefined, then continue.
I thought of using jQuery's .when() -> .done() deferred call but I cant seem to get it to work. I've also thought of doing a loop that would just check to see if its defined yet but that doesnt seem elegant.
Possible option 1:
$.when(_status.getStatus.call(this)).done(function () {
return someScopedVariable;
});
Possible option 2 (Terrible option):
_status.getStatus.call(this)
var i = 0;
do {
i++;
} while (formStatusObject !== undefined);
return formStatusObject;
UPDATE:
I believe I stripped out too much of the logic in order to explain it so I added back in some. The goal of this was to create an accessor to this data.
I would suggest to wait for the complete / success event of an ajax call.
methods = {
init: function () {
_status.getStatus.call(this);
},
continueInit: function( data ) {
// populate 'someScopedVariable' from data and continue init
}
};
_status = {
getStatus: function () {
$.post('webservice.url', continueInit );
}
};
You cannot block using an infite loop to wait for the async request to finish since your JavaScript is most likely running in a single thread. The JavaScript engine will wait for your script to finish before it tries to call the async callback that would change the variable you are watching in the loop. Hence, a deadlock occurrs.
The only way to go is using callback functions throughout, as in your second option.
I agree with the other answer about using a callback if possible. If for some reason you need to block and wait for a response, don't use the looping approach, that's about the worst possible way to do that. The most straightforward would be use set async:false in your ajax call.
See http://api.jquery.com/jQuery.ajax/
async - Boolean Default: true By default,
all requests are sent asynchronously
(i.e. this is set to true by default).
If you need synchronous requests, set
this option to false. Cross-domain
requests and dataType: "jsonp"
requests do not support synchronous
operation. Note that synchronous
requests may temporarily lock the
browser, disabling any actions while
the request is active.

Variables set during $.getJSON function only accessible within function

This may be more of a scoping question. I'm trying to set a JSON object within a $.getJSON function, but I need to be able to use that object outside of the callback.
var jsonIssues = {}; // declare json variable
$.getJSON("url", function(data) {
jsonIssues = data.Issues;
});
// jsonIssues not accessible here
A similar question like this one was asked in another post, and the consensus was that anything I need to do with the JSON objects needs to be done within the callback function, and cannot be accessed anywhere else. Is there really no way that I can continue to access/manipulate that JSON object outside of the $.getJSON callback? What about returning the variable, or setting a global?
I'd appreciate any help. This just doesn't seem right...
UPDATE:
Tried setting the $.ajax() async setting to false, and running through the same code, with no luck. Code I tried is below:
var jsonIssues = {}; // declare json variable
$.ajax({ async: false });
$.getJSON("url", function(data) {
jsonIssues = data.Issues;
});
// jsonIssues still not accessible here
Also, I've had a couple responses that a global variable should work fine. I should clarify that all of this code is within $(document).ready(function() {. To set a global variable, should I just declare it before the document.ready? As such:
var jsonIssues = {};
$(document).ready(function() {
var jsonIssues = {}; // declare json variable
$.getJSON("url", function(data) {
jsonIssues = data.Issues;
});
// now accessible?
}
I was under the impression that that a variable declared within document.ready should be "globally" accessible and modifiable within any part of document.ready, including subfunctions like the $.getJSON callback function. I may need to read up on javascript variable scoping, but there doesn't seem to be an easy to achieve what I'm going for. Thanks for all the responses.
UPDATE #2:
Per comments given to answers below, I did use $.ajax instead of .getJSON, and achieved the results I wanted. Code is below:
var jsonIssues = {};
$.ajax({
url: "url",
async: false,
dataType: 'json',
success: function(data) {
jsonIssues = data.Issues;
}
});
// jsonIssues accessible here -- good!!
Couple follow-up comments to my answers (and I appreciate them all). My purpose in doing this is to load a JSON object initially with a list of Issues that the user can then remove from, and save off. But this is done via subsequent interactions on the page, and I cannot foresee what the user will want to do with the JSON object within the callback. Hence the need to make it accessible once the callback complete. Does anyone see a flaw in my logic here? Seriously, because there may be something I'm not seeing...
Also, I was reading through the .ajax() jQuery documentation, and it says that setting async to false "Loads data synchronously. Blocks the browser while the requests is active. It is better to block user interaction by other means when synchronization is necessary."
Does anyone have an idea how I should be blocking user interaction while this is going on? Why is it such a concern? Thanks again for all the responses.
$.getJSON is asynchronous. That is, the code after the call is executed while $.getJSON fetches and parses the data and calls your callback.
So, given this:
a();
$.getJSON("url", function() {
b();
});
c();
The order of the calls of a, b, and c may be either a b c (what you want, in this case) or a c b (more likely to actually happen).
The solution?
Synchronous XHR requests
Make the request synchronous instead of asynchronous:
a();
$.ajax({
async: false,
url: "url",
success: function() {
b();
}
});
c();
Restructure code
Move the call to c after the call to b:
a();
$.getJSON("url", function() {
b();
c();
});
Remember that when you supply a callback function, the point of that is to defer the execution of that callback until later and immediately continue execution of whatever is next. This is necessary because of the single-threaded execution model of JavaScript in the browser. Forcing synchronous execution is possible, but it hangs the browser for the entire duration of the operation. In the case of something like $.getJSON, that is a prohibitively long time for the browser to stop responding.
In other words, you're trying to find a way to use this procedural paradigm:
var foo = {};
$.getJSON("url", function(data) {
foo = data.property;
});
// Use foo here.
When you need to refactor your code so that it flows more like this:
$.getJSON("url", function(data) {
// Do something with data.property here.
});
"Do something" could be a call to another function if you want to keep the callback function simple. The important part is that you're waiting until $.getJSON finishes before executing the code.
You could even use custom events so that the code you had placed after $.getJSON subscribes to an IssuesReceived event and you raise that event in the $.getJSON callback:
$(document).ready(function() {
$(document).bind('IssuesReceived', IssuesReceived)
$.getJSON("url", function(data) {
$(document).trigger('IssuesReceived', data);
});
});
function IssuesReceived(evt, data) {
// Do something with data here.
}
Update:
Or, you could store the data globally and just use the custom event for notification that the data had been received and the global variable updated.
$(document).ready(function() {
$(document).bind('IssuesReceived', IssuesReceived)
$.getJSON("url", function(data) {
// I prefer the window.data syntax so that it's obvious
// that the variable is global.
window.data = data;
$(document).trigger('IssuesReceived');
});
});
function IssuesReceived(evt) {
// Do something with window.data here.
// (e.g. create the drag 'n drop interface)
}
// Wired up as the "drop" callback handler on
// your drag 'n drop UI.
function OnDrop(evt) {
// Modify window.data accordingly.
}
// Maybe wired up as the click handler for a
// "Save changes" button.
function SaveChanges() {
$.post("SaveUrl", window.data);
}
Update 2:
In response to this:
Does anyone have an idea how I should be blocking user interaction while this is going on? Why is it such a concern? Thanks again for all the responses.
The reason that you should avoid blocking the browser with synchronous AJAX calls is that a blocked JavaScript thread blocks everything else in the browser too, including other tabs and even other windows. That means no scrolling, no navigation, no nothing. For all intents and purposes, it appears as though the browser has crashed. As you can imagine, a page that behaves this way is a significant nuisance to its users.
maybe this work, works to me.. :)
$variable= new array();
$.getJSON("url", function(data){
asignVariable(data);
}
function asignVariable(data){
$variable = data;
}
console.log($variable);
Hope it help you..
:)
You could approach this with promises:
var jsonPromise = $.getJSON("url")
jsonPromise.done(function(data) {
// success
// do stuff with data
});
jsonPromise.fail(function(reason) {
// it failed... handle it
});
// other stuff ....
jsonPromise.then(function(data) {
// do moar stuff with data
// will perhaps fire instantly, since the deferred may already be resolved.
});
It is pretty straight forward and a viable way to make async code feel more imperative.
"But this is done via subsequent interactions on the page, and I cannot foresee what the user will want to do with the JSON object within the callback."
The callback is your opportunity to set the screen up for the user's interaction with the data.
You can create or reveal HTML for the user, and set up more callbacks.
Most of the time, none of your code will be running. Programming an Ajax page is all about thinking about which events might happen when.
There's a reason it's "Ajax" and not "Sjax." There's a reason it's a pain to change from async to sync. It's expected you'll do the page async.
Event-driven programming can be frustrating at first.
I've done computationally intensive financial algorithms in JS. Even then, it's the same thing--you break it up into little parts, and the events are timeouts.
Animation in JavaScript is also event driven. In fact, the browser won't even show the movement unless your script relinquishes control repeatedly.
You are just running into scoping issues.
Short answer:
window.jsonIssues = {}; // or tack on to some other accessible var
$.getJSON("url", function(data) {
window.jsonIssues = data.Issues;
});
// see results here
alert(window.jsonIssues);
Long answers:
Scoping issue in Javascript
Javascript closure scoping issue

Categories

Resources