I got a question concerning asynchronous calls. What I try to do is validate if a given "code" is valid. Refreshing the page is not an option.
The case in a nutshell:
I need to set a variable to true or false based on the reply from the server.
That call is done by using ajax.
The ajax ($.post) request processes the reply from the server and thus sets the variable to true or false.
The value of the variable will then be used as the return value for that function, true or false.The problem is to only return AFTER the ajax has completed. The asynchronous behavior of ajax prevents this and hanging the browser is not done.
The validation is done by using a function (it will en up being used in an if).
The if looks the following:
if(codeGiven()) {
// Code ok. Continue to process
} else { msgCodeInvalid(); }
The function codeGiven is the following
function codeGiven(toChk) {
var codeOK = false; // default answer
if(toChk) { // only perform call if toChk is not "" of undefined
$.post(
"validate.aspx", // server script
{ code: toChk }, // value
function(data) { // function
if(data == "true") {
codeOK = true; // when true => return will be true
} else {
codeOK = false; // when false => return will be false
}
}
);
}
return codeOK; // will be false by default but true when the code is OK
}
the aspx will use the posted "code" and match it against other codes in my database. If a match is found, the reply will be "true". If not, the reply will be "false".
Because an ajax call ($.post) is asynchronous, the result will always be false.
Any suggestions on how to hack my way out of this, besides my idea with a setInterval (a dirty method I know)? A return inside the post didn't work.
Any ideas?
Thanks in advance
Final solution
I came to the realization that I could put my entire logic inside the $.post.
so basically, my logic is the following (warning, it's a deep tree structure):
if(myvar) { // if pid not empty
$.post( // start validation
"validator.aspx", // server side validation script
{ code : myvar }, // data to be passed to the server
function(data, status) { // execute when post happend
if(data == "true") { // if answer from server == "true"
/*
assign variables
apply other logic
process effects
...
*/
} else { /* if answer from server == "false" => error: invalid code given */ }
}
).error( /* error: validator did a boom boom. We're sorry :( */ );
} else { /* error: please enter a code */ }
Do your check in the callback or promise of your function, for example:
function checkCodeGiven(toChk) {
return $.post("validate.aspx", { code: toChk });
}
And then something like:
checkCodeGiven(someVar).then(function(data) {
if(data == "true") {
// do whatever you wanted to do for true
} else {
// do whatever you wanted to do for false
}
});
you can :
. call the function you want inside the success function of the ajax
OR
. the delayer way :calling the part where you need the if(codeGiven) many times with delays about 100ms so you are sure that it's false or it becomes true at one call, replace this part
if(codeGiven()) {
// Code ok. Continue to process
} else { msgCodeInvalid(); }
with codeGiven();Delayer(0); and declair this
function Delayer(counter) {
if (codeOK) {
// Code ok. Continue to process
} else if (counter <10) { //not sure if it may become true later
setTimeout(Delayer, 100, counter++);} //call after 0.1 sec
else {
msgCodeInvalid(); // no hope to be true
}
}
plus codeOK should be declaired globally
hope this works for you
EDIT just clarified a bit
Related
I'm stuck in a really bizarre situation here. It's complicated to explain but I'll try my best.
I have a UI with 4 navigation <a> buttons on top - in the center there's always a form - and at the bottom I have Previous & Next buttons.
Forms are constructed in MVC using Ajax.BeginForm
For each Nav link <a> element on top, I have a JavaScript function
var LoadTabs = function (e, arg) {
// This is to validate a form if one of the top links is clicked and form has incomplete fields...
if (arg !== "prev" && arg !== "next") {
if (!window.ValidateForm(false)) return false;
}
var url = $(this).attr('data'); // this contains link to a GET action method
if (typeof url != "undefined") {
$.ajax(url, { context: { param: arg } }).done(function (data) {
$('#partialViewContainer').html(data);
});
}
}
This function above binds to each top link on page load.
$('.navLinks').on('click', LoadTabs);
My Next & Previous buttons basically trigger the click event i.e. LoadTabs function.
$('button').on('click', function () {
if (this.id === "btnMoveToNextTab") {
if (!window.ValidateForm(true)) return false;
$.ajax({
url: url,
context: { param: 'next' },
method: "GET",
data: data,
success: function(response) {
if (typeof response == 'object') {
if (response.moveAhead) {
MoveNext();
}
} else {
$('#mainView').html(response);
}
ScrollUp(0);
}
});
}
if (this.id === "btnMoveToPreviousTab") {
MoveBack();
}
return false;
});
MoveNext() Implementation is as below:
function MoveNext() {
var listItem = $('#progressbarInd > .active').next('li');
listItem.find('.navLink').trigger('click', ['next']);
ScrollUp(0);
}
The problem is, for some reasons, when Nav Link 3 is active and I hit the NEXT button - Instead of posting the form first via form.submit() - the nav 4 gets triggered - hence GET for nav 4 runs before form POST of nav 3.
My ValidateForm method is basically just checking if the form exists and is valid then Submit, else returns false. It's as below:
function ValidateForm(submit) {
var form = $('form');
// if form doesn't exist on the page - return true and continue
if (typeof form[0] === "undefined") return true;
// now check for any validation errors
if (submit) {
if (!$(form).valid()) {
return false;
} else {
$(form).submit();
}
}
else {
return true;
}
return true;
}
My speculation is that form.submit does get triggered as it should be but since submit takes a little longer to finish it continues with the next code block in the button onclick event.
I first thought that this is C# issue as in the POST I'm saving a big chunk of data with a few loops, and any code block that's process heavy I have that part in
var saveTask = Task.Factory.StartNew(() => ControllerHelper.SomeMethod(db, model));
Task.WaitAll(saveTask);
WaitAll will wait and pause the execution until SomeMethod finishes executing. I'm not sure how can I lock a process in javascript and wait for it to finish execution. Because I think If i can somehow lock the form.submit() in ValidateForm until its finished processing .. via a callback method perhaps...
Please if anyone can put me in right direction, I'd greatly appreciate the help. If you need more information please let me know I'd be happy to provide!
I'm posting this is a note to Alan since it's multi-line code and I can't make it readable in a comment. To avoid the promise anti-pattern when you sometimes run an async operation and sometimes don't, one can use this:
function ValidateForm(){
// do your logic
if (pass) {
return $.post("urlToPost");
else {
return $.Deferred().reject(xxx).promise();
}
}
This way, you're always returning a promise. In one case, the promise comes from $.ajax(). In the other case, you're just returning a rejected promise since the operation has already failed.
Edit regard to return promise anti pattern
as jfriend00 commented the way original answer is returning the promise is anti pattern, better way would be:
function ValidateForm(){
// do your logic
if (pass) {
return $.post("urlToPost");
else {
return $.Deferred().reject(xxx).promise();
}
}
more info on this, checkout https://github.com/petkaantonov/bluebird/wiki/Promise-anti-patterns
original answer
I will try picture your problem:
what you want is run ValidateForm first, when it passess (such that when form post is done), then do MoveNext
your problem is, MoveNext is called before ValidateForm has finished.
If I am correct, then the reason is because Javascript by nature is async programing, hence use callback to fire when something has done.
So in your situation, you need to use promise to acheive it.
essentialy what you need to do is:
let ValiateForm returns promise
only execute MoveNext once the ValidateForm has finish
So code would be like:
function ValiateForm(){
var dfd = jQuery.Deferred();
// do your logic
if(pass){
$.post("urlToPost"
} else {
// also failed
dfd.reject();
}
// return the promise
return dfd.promise();
}
then your move next button would be like
$('button').on('click', function () {
if (this.id === "btnMoveToNextTab") {
$.when(window.ValidateForm(true)).then(function(){
// after success post form
$.ajax({
url: url,
context: { param: 'next' },
method: "GET",
data: data,
success: function(response) {
if (typeof response == 'object') {
if (response.moveAhead) {
MoveNext();
}
} else {
$('#mainView').html(response);
}
ScrollUp(0);
}
});
if (this.id === "btnMoveToPreviousTab") {
MoveBack();
}
}
return false;
});
});
more info on promise checkout
https://api.jquery.com/deferred.promise/
and for run when finish
https://api.jquery.com/deferred.then/
How to execute three services in a synchronous way in AngularJS? I have three $http.get() services and on its success have to read the JSON fields and if the particular set of fields have valid data,a flag has to be set to true/false and depending on the flag result the next service will be called otherwise not.But here, the services are running asynchronously so my logic is failing.
Sample Code:
// Condition 1
if(item === false) {
var product = Service1.get().then(function (response) {
// Logic for reading the JSON
// Setting the flag based on it..
item = true/false;
}
//Condition 2
if(item === false) {
var call = Service2.get().then(function (data) {
// Logic for reading the JSON
// Setting the flag based on it..
item = true/false;
}
}
// Condition 3
if(item === false) {
var product = Service3.get().then(function (response) {
// Logic for reading the JSON
// Setting the flag based on it..
item = true/false;
}
}
}
Here, the problem is that code in *Condition3* is getting executed first then code in *Condition1* and *Condition2* which is causing the unexpected results.
It would be of great help if someone has the sample code in which three services are executed in a sequential manner.
Instead of executing a new $http request in the success handler and write cascades of requests, perhaps you can solve it in a recursive way:
function recursiveHttp(array) {
$http(array[0].config).then(function() {
array[0].success();
recursiveHttp(array.splice(0,1));
}, function() {
array[0].error();
recursiveHttp(array); //beware: no escape clause
});
}
Where the array is a collection of objects that contain the required config object and two callback functions.
{
config : {
method: 'get',
url: 'myurl'
},
success: function() {
//do stuff
},
error: function() {
//do stuff
}
}
There are 2 ways to achieve what you want (as far as i can make out from your question):
change the behavior of $http.get to async by using async : true.
chain your requests properly so that one executes only after the other, so that each has their dependencies met before they start executing. this ca be done by calling the dependent function on the callback of the first one.
This is the block that checks if returned value = 1:
} else if (validateUsername(username.val()) == 1) {
errors.html("That username already exists! Please use another.<span id='close'>X</span>");
errors.fadeIn("slow");
username.css("border", "solid 2px red");
}
If yes, username exists..
Now let's do the ajax work.
function validateUsername(username) {
$.post("js/ajax/ajax.php", { validateUsername : username }, function(data) {
return data;
});
}
This will send a request to ajax, and fill var data with the response.
if (isset($_POST['validateUsername']))
{
echo 1;
}
this is the ajax that will send a response, basically it's int 1 for now, for checking.
But, I always get undefined.
I mean, fireBug does say "request=1", but it looks like the if statement won't work.
If I just do return 1; manually without ajax, it will work.
Why is it doing that?
Ajax is asynchronous, so when you return data here :
function validateUsername(username) {
$.post("js/ajax/ajax.php", { validateUsername : username }, function(data) {
return data;
});
}
and try getting it here :
validateUsername(username.val()) == 1
it won't work, as the ajax call hasn't completed yet (and the return returns from the inner function only).
Instead you should do:
function validateUsername(username) {
return $.post("js/ajax/ajax.php", { validateUsername : username });
}
validateUsername(username.val()).done(function(data) {
if ($.trim(data) === 1) {
errors.html("That username already exists! Please use another.<span id='close'>X</span>");
errors.fadeIn("slow");
username.css("border", "solid 2px red");
}
});
Ajax functions are asynchronous. validateUsername() returns before the ajax call is complete (without a return value, which is why you see undefined).
Probably the most elegant way to handle this would be to refactor to return $.ajax... and work with the Deferred object that is returned.
AJAX stands for Asynchronous JavaScript And XML. Ignore the XML part, but the Asynchronous is what's biting you in the backside here.
Anything that depends on the result of an ajax call must be in the success handler, called by the success handler, or defined in such a way that it will only be run after the success handler.
Not sure if my question is subjective/objective but as a JavaScript newbie i'm encountering this problem quite a lot. So here I go.
I'm used to write C#, so my JavaScript structure looks like C#. And just that, that gives problems I think ;-)
Let's give a simple example where I met my problem again today:
MyLibrary.fn.InitAddEntityForm = function () {
$('a#btnAddEntity').click(function () {
//post data and receive object with guid and isPersisted boolean
var persistedObject = MyLibrary.fn.CheckAndSendAddEntityForm("name", "avatarurl.png");
console.log("test");
//check if persisted and go to next step
if (persistedObject.isPersisted) {
MyLibrary.fn.InitAddAnotherEntityForm(persistedObject.gdEntityId);
} else {
alert("Oops, something went wrong. Please call 911");
}
});
};
//////*****/////
//SOME FUNCTION THAT SENDS MY FORM AND RETURNS AN OBJECT WITH TRUE VALUE AND POSTED ENTITY ID
/////*****//////
MyLibrary.fn.CheckAndSendAddForm = function (txtName, ImageUrl) {
var postUrl = "/admin/add";
var persistedObject = new Object();
$.post(
postUrl,
{ Name: txtName, ImageUrl: txtImageUrl},
function (data) {
if (data.Status == 200) {
console.log("Post status:" + data.Message);
persistedObject.isPersisted = true;
persistedObject.gdEntityId = data.Data;
} else if (data.Status == 500) {
console.log("Failed to post entitiy");
} else {
console.log("Fault with Javascript");
}
}, "json"
);
return persistedObject;
};
Okay, thats it. Everything looks okay right? Browser says no.
I tried to debug it using firebug, looping over my code line by line, and that way the browser does what I want: Execute a new function to show the next panel in my wizard.
After placing a lot of Console.logs() in my code I figured out that this must be something about timing in JavaScript. In C# the code executes line by line, but apparently JavaScript doesn't.
By placing that Console.log("test") I noticed that "test" appeared in my console before "Post status: Success!".
So here's my question, how should I write my JavaScript code so I have control over the way the browser executes my code?
Should I really replace the code below to the end of my CheckAndSendAddEntityForm()?
//check if persisted and go to next step
if (persistedObject.isPersisted) {
MyLibrary.fn.InitAddAnotherEntityForm(persistedObject.gdEntityId);
} else {
alert("fout");
}
Is this how I have to write JavaScript: One big domino effect or am I just doing something wrong?
$.post is a shortcut for an AJAX call, AJAX is by definition asynchronous, which means it won't wait on a response before continuing processing. If you switch it to a regular AJAX() method, there is an async option you can set to false, which will make it behave as you are expecting.
Alternatively you can also define a function to execute on successful return of the AJAX request, in which you can call the next step in your process chain.
The AJAX call is asychronous; that means that the callback method exposes by $.post will be executed when the request completes, but your javascript will continue executing as soon as the invoke to $.post finishes. If you want to do something after the ajax call is done, you need to provide a callback method and do something else, ex:
MyLibrary.fn.CheckAndSendAddForm = function (txtName, ImageUrl, callback) {
var postUrl = "/admin/add";
var persistedObject = new Object();
$.post(
postUrl,
{ Name: txtName, ImageUrl: txtImageUrl},
function (data) {
if (data.Status == 200) {
console.log("Post status:" + data.Message);
persistedObject.isPersisted = true;
persistedObject.gdEntityId = data.Data;
} else if (data.Status == 500) {
console.log("Failed to post entitiy");
} else {
console.log("Fault with Javascript");
}
callback(); // This is where you return flow to your caller
}, "json"
);
};
Then you invoke like so:
var persistedObject = MyLibrary.fn.CheckAndSendAddEntityForm("name", "avatarurl.png", function()
{
console.log("test");
//check if persisted and go to next step
if (persistedObject.isPersisted) {
MyLibrary.fn.InitAddAnotherEntityForm(persistedObject .gdPronoId);
} else {
alert("Oops, something went wrong. Please call 911");
}
});
JavaScript is single-threaded. If you have asynchronous functionality, a simple boolean semaphore variable will help not to allow invocations of a function while some processes are running.
If you want to execute asynchronous tasks one by one (like a domino line), you will need to use callback functions.
What you're encountering is the "asynchronous" bit of AJAX. If you want to physically (as in the line line by line in the Javascript file) you can use the .success,.pipe or .done jQuery methods to add a callback to process the data further. Don't embed your callbacks if you can help it, or you will get a "domino effect" as you call it.
I am making recursive calls to a URL until it returns success or has hit the max tries limit. Here is the relevant code, (minified, so to speak):
function doSomething(numRetries) {
$.post('someURL', {retry: numRetries},
function (data) {
if (data.value == 1) {
displayResults(data.message, data.value);
} else if (data.value == "retry") {
setTimeout( function() { doSomething(data.retries) }, 1000);
} else {
displayResults(data.message, data.value);
}
},
"json"
);
}
IFF the first call to sumeURL returns data.value == 1, it executes displaySuccess. Similarly, if it returns another value (e.g. 0), it will displayFailure() successfully.
The problem lies in the recursive part. After it kicks off the retries, it does call doSomething() again with an incrementing retries value, but any return data after that is not used.
So when my retry timeout inside someURL is 3, for example, I can see in firebug:
post('someURL', 0) returns JSONified (value = "retry", retries = 1)
post('someURL', 1) returns JSONified (value = "retry", retries = 2)
post('someURL', 2) returns JSONified (value = 0, error = "Display this error!")
but an alert() inside displayFailure indicates that error = [undefined], even though value = 0 (not "retry"). Firebug indicates proper JSON parsing is occurring.
EDIT modify the doSomething to be a more accurate reflection of reality, though the changes shouldn't introduce any uncertainty, and by request, here are the actual return values from the post calls:
{"success":"retry","retryCount":"1"}
{"success":"retry","retryCount":"2"}
{"success":0,"errormsg":"The request is taking longer than expected, but should be completed soon. Please try again in 15 minutes."}
and lastly here is a minified displayResults():
function displayResults(text, status) {
$('#dispElem').queue(function(next) { //this is so that fades happen around the text update, not before/during it; there may be better ways to do this
$('#dispElem').html(text);
if (status == 1) {
$('#dispElem').addClass("success");
} else {
// hide and show random elements
}
next();
}).fadeIn().queue(function(next) { //scroll to bottom; next(); });
}
Arrrgh.
The worst scenario of all - an uppercase/lowercase mistake I just kept overlooking until forced to retype to minify.
Thanks, all, for the comments and making me recomb it, with a finer tooth, so to speak.