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
});
}());
Related
Wondering what the best solution to this problem is, also this is not my actual code structure or names but the simplest way to illustrate the problem.
I have a function which was purely used to perform an ajax call and load a template with jquery.
function load(template) {
$('#container').load(template, data, function() {
// complete code here
});
}
Focusing on the 3rd param in $.load(), namely a callback function that runs when the request is complete.
Now I have my load() function in another wrapper function:
function processTask(variable) {
load(variable);
}
The problem I have is I need some code to run after the ajax load is complete, however as my app has grown my wrapper function processTask may or may not invoke an ajax load so I can't perform my must needed code inside the complete callback.
Do I change my $.load() to perform synchronous or just manage my code better so that if I am calling a $.load() it puts my needed code in the callback and if not it places it where I need it to be?
I have read about javascript Promises and I'm unsure if they will help in this situation.
EDIT
So my processTask is an object method.
function classObj(name, fn) {
this.name = name;
this.processTask = fn;
this.load = function(template) {
$('#container').load(template, data, function() {
// complete code here
});
}
}
And in context I do this:
var task = new classObj('taskName', function() {
this.load('myFile.php');
// Or another function and not load() based on whats needed in the task.
});
Basically I have an object that I can add custom methods to at will and they can easily be called dynamically, until now they have always loaded a file.
First, change your load function to return the xhr from get (or ajax):
function load(template) {
return $.get('myFile.php', data, function(result) {
$('#container').html(result);
});
}
Then, within your code you can use when then to perform your code after the load completes if applicable:
var xhr;
/* ... */
if(something){
xhr = load(template);
}
/* ... */
if(xhr){
$.when(xhr).then(doSomething);
} else {
doSomething();
}
And in fact, this can be simplified using the fact that a non-deferred object passed to when (including undefined apparently) will execute the then immediately and get rid of the if:
$.when(xhr).then(doSomething);
If xhr is undefined then when will resolve immediately causing then to execute immediately.
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/
I have an asynchronous Ajax function which runs a command string at the server side and returns the result to the client. It calls a callback to process the result.
function ajaxCall(commandStr,callback){
var url=......//make a url with the command string
jquery.get(url,function(result){
//process the result using callback
callback(result);
});
}
The asynchronous call (ajaxCall) may take a while to be finished but I want it to do the same command after an interval (1000ms).
I want to write a function that is like this:
function ajaxCallRepeated(interval,commandStr,callback)
I tried closures like this:
function ajaxCallRepeated(interval,commandStr,callback){
//This feature uses closures in Javascript. Please read this to know why and how: http://jibbering.com/faq/notes/closures/#clSto
function callLater(param1,param2,param3){
return (function(){
ajaxCall(param2,function(out,err){
if(param3)param3(out,err);
var functRef = callLater(param1,param2,param3);
setTimeout(functRef, interval);
});
});
}
//the first call
var functRef = callLater(interval,commandStr,callback);
setTimeout(functRef, interval);
}
Then I call it like this:
ajaxCallRepeated(2000,"ls",function(result){
alert(result);
});
But it only runs the command 2 times.
How can I write a function that will reschedule itself after it is called as a callback of an asynchronous function?
PS. I want to fire another Ajax call after the previous one is finished. Also, it worth to mention that axashCallRepeated() will be called with various parameters, so several Ajax calls are running in parallel, but for each commandStr, there is only one Ajax call going on, and after the Ajax call returns, another one will be fired after X seconds.
I would not use setTimeout to trigger the second Ajax call ! Because you never know how long it will take and if it's finished !
As far as you tagged your question right and you ARE using jquery you should consider something like this:
$.ajax({
type: 'POST',
url: url,
data: data,
success: function(){
// The AJAX is successfully done, now you trigger your custom event:
$(document).trigger('myAjaxHasCompleted');
},
dataType: dataType
});
$(function(){
//somehwere in your document ready block
$(document).on("myAjaxHasCompleted",function(){
$.ajax({
//execute the second one
});
});
});
So this would ensure that the ajax post is DONE and was successful and now you could execute the second one. I know its not the exact answer to your question but you should consider on using something like this ! Would make it safer I guess :-)
The key to solve this problem is to save a reference to the closure itself and use it when scheduling the next call:
function ajaxCallRepeated(interval,commandStr,callback){
//This feature uses closures in Javascript. Please read this to know why and how: http://jibbering.com/faq/notes/closures/#clSto
function callLater(_interval,_commandString,_callback){
var closure=(function(){
ajaxCall(_commandString,function(out,err){
if(_callback)_callback(out,err);
setTimeout(closure,_interval);
});
});
return closure;
}
//now make a closure for every call to this function
var functRef = callLater(interval,commandString,callback);
//the first call
functRef();
}
It becomes easier to reason about if you separate things up a bit.
For example, the repetition logic doesn't have to know about AJAX or callbacks at all:
function mkRepeater(interval, fn, fnScope, fnArgs) {
var running;
function repeat() {
if (!running) return;
fn.apply(fnScope, fnArgs);
setTimeout(repeat, interval);
}
return {
start: function() { running = true; repeat(); },
stop: function() { running = false; }
};
}
You can use it like this:
var r = mkRepeater(2000, ajaxFunction, this, ["getStuff", callbackFn]);
r.start();
...
r.stop();
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.
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.