Form Submission vs AJAX Polling Call - javascript

Following up on my question from the other day, I've run into another thing that now I've spent too many hours banging my head against.
Mostly, I'm having trouble getting the SUCCESS form to submit. I tried this as well:
jQuery form submit
Here's the code in a semi-functional fiddle:
http://jsfiddle.net/ZcgqV/
Essentially what happens is this:
I bind a method to the form's submission via onSubmit (rather than click)
On submit, it calls a remote server via jQuery .ajax() call
If the response is "PENDING", retry every 1s, nine times
On failure, don't submit the form
On success, submit the form
No matter what I try, I can't get the form to either submit when I want it to without going into a loop, or not submit immediately while it tries the remote server.
~Frustrated-trying-100-things-that-fail-ly yours...
Here's the code directly in case you dislike fiddles:
var retries = 0;
var success = false;
var token = "toki wartooth is not a bumblebee";
$(document).ready(function() {
// Attach the action to the form
$('#tehForm').attr('onSubmit', 'onsubmit_action(event)');
});
function async(fn) {
setTimeout(fn, 1000);
}
function pollServer() {
$.ajax({
type: "POST",
cache: "false",
url: "/remoteCall",
dataType: "json",
data: {
ref_token: token
}
}).done(function(data, code, jqXHR) {
switch (data.status) {
case "SUCCESS":
alert("Success");
success = true;
// --> HERE IS WHERE I WANT THE FORM TO SUBMIT <--
break;
case "PENDING":
if (retries < 9) {
retries += 1;
async(function() {
pollServer();
});
} else {
alert("Failed after 9 tries");
}
break;
case "ERROR":
alert("Error");
break;
default:
alert("Some kind of horrible error occurred");
break;
}
}).fail(function(jqXHR, textStatus) {
var statusCode = jqXHR.status;
alert("Request failed: " + statusCode + " " + textStatus);
});
}
function onsubmit_action(event) {
pollServer();
if (success === false) {
// RETURN FALSE DIDN'T WORK, SO I FOUND THIS
event.preventDefault();
}
}​
EDIT:
Again, the real problem here is that I stop submission of the form. On SUCCESS, I want the form to submit. Currently if I use .submit() in SUCCESS, the AJAX is called again, starting the process over. What I want is the ACTION of the FORM to fire on SUCCESS only.

Trying to use as much of the original code as possible; here is a solution:
Post form with post back
http://jsfiddle.net/tpm7v/4/
Post form via Ajax
http://jsfiddle.net/tpm7v/5/
var retries = 0,
token = "toki wartooth is not a bumblebee",
sendRequest,
handelResponse,
postFormToServer,
$theForm = $('#tehForm');
$(document).ready(function() {
// Attach the action to the form
$theForm.bind('submit', onsubmit_action);
});
sendRequest = function() {
$.ajax({
type: "POST",
cache: "false",
url: "/remoteCall",
dataType: "json",
data: {
ref_token: token
},
success: handelResponse
});
};
postFormToServer = function() {
$.ajax({
type: "POST",
cache: "false",
url: "/remoteCallToTakFormData",
dataType: "json",
data: $form.serialize(),
success: function() {
alert('success!');
}
});
};
handelResponse = function(data, code, jqXHR) {
switch (data.status) {
case "SUCCESS":
postFormToServer();
break;
case "PENDING":
if (retries < 9) {
retries += 1;
setTimeout(sendRequest , 1000);
} else {
alert("Failed after 9 tries");
}
break;
case "ERROR":
alert("Error");
break;
default:
alert("Some kind of horrible error occurred");
break;
}
};
function onsubmit_action(evt) {
evt.preventDefault();
sendRequest();
}
​
​
Keep in mind I am going off the code your provided. You should be able to port this to work with your actual implementation. You may also want to try something like https://github.com/webadvanced/takeCommand to help clean up all the Ajax calls.

Please see my comment above for more information, but I think the problem you're seeing here is this:
Every time pollServer() fires, it's not only doing another ajax call, but it's prepping to do 9 possible ajax calls every second based on the retries loop. Since you're then setting another pollServer() call with the async() method, you're basically compounding your ajax calls out of control. You want to get the ajax call out of your retry loop, then you should at least be only getting 1 request a second, not 1, then 2, then 3, etc. I may have read the code wrong, but this is my best guess on what you're seeing.
UPDATE: I'm not sure my explanation was clear, so I thought I'd add some additional info. Basically, every time pollServer() is called and gets a PENDING response, it calls async, which registers a setTimeout(). setTimeout() keeps running every second, doing pollServer(), which then calls asynch, which registers another setTimeout() which also runs every second. Now you have two functions, which each then call setTimeout(), assuming they're still getting PENDING as a response from the server. So after 2 rounds of failed calls, you have 4 setTimeout() calls each firing an ajax call (and a new setTimeout) every second.

First off it should be: $('#tehForm').submit(onsubmit_action); or $('#tehForm').on("submit",onsubmit_action); or something like that. Never use the string form to pass a function. It uses the evil eval statement.
Next, after POST the data is already submitted. That is the whole reason for post. Why do you need all sorts of error handling in the done section. Fail should handle error handling.
If you are asking about how to try again after a timeout, try this:
Is it possible to check timeout on jQuery.post()?
I believe timeout will fall into fail.
So try this:
var retries = 0,
max_tries = 9,
success = false,
token = "toki wartooth is not a bumblebee";
$(document).ready(function() {
// Attach the action to the form
$('#tehForm').on("submit",submit_the_form);
});
function submit_the_form(e){
var dfd = $.ajax({
url : "sendTokenPolling",
data : {"token":token},
timeout : 5000 //you may want 1000, but I really think that is too short
});
dfd.done(function(){
//success, form posted
});
dfd.fail(function(){
//did not work/timedout
if (retries < max_tries){
retries += 1;
submit_the_form(e);
}
});
}

Related

Wait for $.ajax result inside .each function

I have function that search for every element with a specific class:
$("#stepSurveyCtnId .questionCtnClass").each(function () {}
Inside each step, I check if a question is of type customer:
var type = $(this).children().data("question-type");
var isCustomerQuestion = false;
switch (type) {
case "Name":
case "Email":
isCustomerQuestion = true;
break;
}
If it's customer type, I get the next id of the customer's table from the database:
if(isCustomerQuestion) {
if (customerId == -1) {
$.ajax({
method: "POST",
url: urlCustomerCreate,
success: function (ajaxData) {
customerId = ajaxData.NumericValue;
}
});
}
}
The issue is that in the second iteration of the .each() function, customerId is still = -1, when it should be 1305 for example.
It seems that the execution don't stop in the $.ajax call, or the iterations are executed at the same time and the second iteration don't receive the customerId from the first iteration.
I'm still not 100% clear on sure on how everything is structured for you, but here is one way of handling asynchronicity in JavaScript (adapted from #ShubHam's answer)
function handleQuestion(questionElements, index, customerId) {
if (questionIndex >= questionElements.length) return;
var type = $(this).children().data("question-type");
var isCustomerQuestion = false;
switch (type) {
case "Name":
case "Email":
isCustomerQuestion = true;
break;
}
if(isCustomerQuestion) {
if (customerId == -1) {
$.ajax({
method: "POST",
url: urlCustomerCreate,
success: function (ajaxData) {
handleQuestion(questionElements, questionIndex + 1, ajaxData.NumericValue);
}
});
} else {
// Have ID now
handleQuestion(questionElements, questionIndex + 1, customerId);
}
}
}
// Go
handleQuestion($("#stepSurveyCtnId .questionCtnClass"), 0, -1);
This will only continue to the next iteration after the success callback has been triggered.
Put logic inside one function (say function 1) and ajax call inside other function.
Call ajax function from function 1. Inside success call function 1 with required params
Update (example added):
var x=['a','b','c']
var elm=document.getElementById('demo')
x.forEach(function(temp){
elm.innerHTML=elm.innerHTML+temp
})
<div id='demo'></div>
This can be converted to new logic as
var x=['a','b','c']
function sethtml(temp,length,maxlength){
//here ajax call can be placed
var elm=document.getElementById('demo')
elm.innerHTML=elm.innerHTML+temp
//inside success function of ajax
traverse(length+1,maxlength)
}
function traverse(length,maxlength){
if(length>=maxlength)
{
//all calls done next steps to perform
}else{
sethtml(x[length],length,maxlength)
}
}
traverse(0,x.length)
<div id='demo'></div>
Advice to be considered from Jamie-Day in comments: Check your logic for scope of improvement. Accessing db results in for each kind of scenario generally can be avoided(ideally it should be avoided for better user experience)
Change your ajax code. add "async: false" so that each code next to ajax will wait for ajax result
if(isCustomerQuestion) {
if (customerId == -1) {
$.ajax({
method: "POST",
async: false,
url: urlCustomerCreate,
success: function (ajaxData) {
customerId = ajaxData.NumericValue;
}
});
}
}
First, you need to think asynchronously.
Code that need to run after the ajax should be called from the success function. You also want to add error function to handle server errors.
Second, to improve speed and bandwidth I'd reduce number of AJAX calls to a single one, by joining all IDs together in a single AJAX request.
It require server-side changes and you did not provide the server-side, so I'll leave server side to you.
// Prepare ajax call
var customerData = [];
var customerCreateData = [];
$("#stepSurveyCtnId .questionCtnClass").each(function () {
var type = $(this).children().data("question-type");
var isCustomerQuestion = false;
switch (type) {
case "Name":
case "Email":
isCustomerQuestion = true;
break;
}
// Set customerId and customerCreateData
if(isCustomerQuestion) {
if (customerId == -1) {
customerCreateData.push(customerCreateData);
}
}
}); // end each
if (customerCreateData.length) {
$.ajax({
method: "POST",
url: urlCustomerCreate,
data: customerCreateData,
success: function (ajaxData) {
customerData = ajaxData.customerData;
doAmazingThingsWithCustomers(customerData);
},
error: function(jqXHR, textStatus, errorThrown) {
alert('Server error: ' + errorThrown);
}
});
}
The first A in AJAX stands for Asynchronous which means that the ajax calls would get executed and would not wait for the call to finish. This way we can let users interact with other elements on the page and provide a good user experience.
If we make the AJAX calls asynchronous by setting the async option to false, then the browser would wait for the ajax call to complete and users would not be able to interact with the elements until the call has completed. With the increase in number of calls, this blockage time would increase.
I would suggest you find a better alternative than this.

How to correctly handle ajax timeouts

For example, a user want to login, the connection is slow or the request is stuck into some network , then the user waits, but sometimes is better resend the request than waiting.
Questions:
What would be the desirable waiting time? (no uploading files, just
simple login) I've put 15 secs, but maybe it's too much.
What's the best solution?
1) Keep the user waiting till he decides to click login again
2) Set an ajax timeout
$.ajax({
url: '{{ url('/login') }}',
data: data,
method: 'POST',
timeout: 15000,
and display them an error
error: function(data, status, error){
if(status==="timeout") {
var errorString = 'Please retry. Ref Timeout';
}
3) do an auto retry (code)
$.ajax({
url : 'someurl',
type : 'POST',
data : ....,
tryCount : 0,
retryLimit : 3,
...
error: function(data, status, error){
if (status == 'timeout') {
this.tryCount++;
if (this.tryCount <= this.retryLimit) {
//try again
$.ajax(this);
return;
}
return;
}
4) Use a wrapper function over the ajax
setTimeout(function(){
$.ajax({...})
}, 15000);
5) Some other options
I would personally do a mix, that means, try 2 times and the fall, you can use this code:
$.ajax({
url: '{{ url('/login') }}',
data: data,
method: 'POST',
timeout: 10000, // sets timeout to 5000 = 5 seconds
retryCount: 0, // start retry count
retryLimit: 1, //will let you retry a determined number of times
error: function(data, status, error){
if(status==="timeout") {
this.retryCount++;
if (this.retryCount <= this.retryLimit) { //&& Date.now() - this.created < this.retryTimeout
console.log("Retrying");
$.ajax(this);
return;
}
else{
var errorString = 'Timeout';
}
Default server timeout is 30s, so it's proper timeout in Ajax.
Don't bombard server with re-logins (if it's too busy, you make it even worse).
Do not allow user to click login button once more while request is pending.
IMO there should be ajax without timeout and on error you should tell user to try again later.
$.ajax({
error: function (response) {
console.error(response); // Show error response to dev
alert('Something went wrong. Please try again later or contact administrator admin#email.com'); // Use pretty modal instead
}
})
You could you a library like https://github.com/inmar/patience_js that lets you define retry strategies and keep your code a lot cleaner.
Or better yet take a look at RxJS
where you can use an approach like the one suggested here: RxJS retry operator with ajax call
const doLogin = () => {
console.log('calling');
return $.ajax('...')
};
const stream = Rx.Observable.fromPromise(doLogin).retry(3);
stream.subscribe(log);

Javascript redirect on Ajax success

I have a quiz type application. Each question has two answers with a value of 1 or 2. When the quiz is complete, if they have a score lower than 10, they get redirected to a page.
This is my code for this part.
while (n < numResults) {
increment = minScore + (interval * n);
if (totalScore <= increment) {
if(totalScore <= 10) {
$.ajax({
method: "POST",
url: "handleData.php",
dataType: "json",
data: { answers: ansArray, page: window.location.href }
})
.done(function( msg ) {
window.location.href("www.page2.html");
});
}
return;
} else {
n++;
}
}
I have a few things I am trying to solve. Firstly, before the redirect, some data (answers and url) is posted to PHP so I can process it. One thing I pass is the current window url. The reason I do this is because the
url has a format like
www.page1.com?a=1&b=2&c=3
In PHP, I parse this url and grab the values.
My first problem is that although the data is successfuly sent to PHP and handled, and returns a response of Success, the done function never seems to fire, therefore no redirect occurs (I put an alert in this function
to ensure it is not firing). In PHP, after I process the data, I do this
var_dump($response); //Outputs Success
return json_encode($response);
The second thing I am trying to work out is the redirect url (page2.html). Within this page, I have a button. This button has a standard link, and then I need to give it some params from the initial url.
So this pages button might be something like
www.externallink.com?a=1&c=2
How can I get the original URLs params into a button on the redirected url?
Thanks
USE below function insted of done:
$.ajax({
method: "POST",
url: "handleData.php",
dataType: "json",
data: { answers: ansArray, page: window.location.href }
success:function(data){
window.location.href("www.page2.html");
});
})
For your 1st part:
Try putting the error function of jQuery ajax call. Sometimes when the return type of result does not match with the expected datatype of ajax call, then result comes in the response of error.
error: function (data, status, e) {
}
For your 2nd part:
Attach click event for the button in the handler and read the current URL query string using window.location.search and then redirect using
window.location = newURL + "extra query params";
// Assign handlers immediately after making the request,
// and remember the jqXHR object for this request
var jqxhr = $.ajax( "example.php" )
.done(function(data, textStatus, jqXHR) {
alert( "success" );
})
.fail(function(jqXHR, textStatus, errorThrown) {
alert( "error" );
})
.always(function(data|jqXHR, textStatus, jqXHR|errorThrown) {
alert( "complete" );
});
If you .done() callback never invoked, try to set debuggers or alerts inside .fail() or .complete() callback functions. Check if you have an error during ajax call and at all if the call has complete statement.
Here more information: http://api.jquery.com/jquery.ajax/

Prototype validation not working correctly

I use Prototype.js to validate a form. For one of the fields, I have the prototype script ajax a request to a file. The file is a simple PHP file and will return '1' if the value is OK and '0' if the value is not OK. I have the script as below, which should work perfectly. The prototype validation is supposed to show a validation error message when a field does not pass validation, and not display / remove the message once the field passes validation. But in this case, even when the ajax file returns '1', the validation will display the error message anyway. Anyone able to help would be greatly appreciated!
['validate-number3', numessage3, function(v) {
new Ajax.Request('test.php?nr='+v, {
method:'get',
onSuccess: function(transport) {
var response = transport.responseText;
if(response == '1'){return true;}else{return false};
}
});
}],
the return value from Ajax.Request is the Ajax.Request object and returns as soon as the request is setup - the onsuccess callback is called after the request has been completed - so checking the results of Ajax.Request is not useful for what you want to accomplish.
The reason that this doesn't work as you expect, this is an asynchronous call which means it will start the call and then return control to the script while it is processing and then run the callbacks when it is completed.
Try it this way
new Ajax.Request('test.php?nr='+v, {
method:'get',
onSuccess: handleResponse
});
function handleResponse( transport ){
var response = transport.responseText;
if(response == '1'){
//everything is OK
}else{
//value is not OK
};
}
I was able to solve my question!
Thanks to this teriffic page: http://inchoo.net/ecommerce/magento/magento-frontend/magento-form-field-ajax-validation/ it was no problem. This is what I ended up with:
var ok = false;
new Ajax.Request('test.php?nr='+v, {
method:'get',
asynchronous: false,
onSuccess: function(transport) {
var response = transport.responseText;
if(response == '1'){ok = true;}else{ok = false;};
},
onComplete: function() {
if ($('advice-validate-number-pay_bank_no')) {
$('advice-validate-number-pay_bank_no').remove();
}
}
});
return ok;

How to stop all active or pending ajax requests?

In my rails app, using jquery slider. In stop event of slider, there is ajax request. If user continuously sliding slider there are too many pending ajax requests and my system get hang. I have used:
1:
function slide_stop(event, ui){
$.xhrPool = [];
$.xhrPool.abortAll = function() {
$(this).each(function(idx, jqXHR) {
jqXHR.abort();
});
$.xhrPool.length = 0
};
$.ajaxSetup({
beforeSend: function(jqXHR) {
$.xhrPool.push(jqXHR);
},
complete: function(jqXHR) {
var index = $.xhrPool.indexOf(jqXHR);
if (index > -1) {
$.xhrPool.splice(index, 1);
}
}
});
$.xhrPool.abortAll();
$('span#imgLoader').html('<img src="/assets/ajax-loader.gif">');
$.ajax({
type: 'get',
dataType: 'json',
url: 'some_url',
data: { is_ajax: true }
}).done(function(response){
$('span#imgLoader').empty();
});
}
Initialize slider,
$elt.slider({
min:0,
max:100,
value:50,
slide: slide_range,
stop: slide_stop
}).each(function(){
add_range_label($range_elt);
});
All ajax requests get stopped/in not modified state. But last request takes too long time to complete. No result again same hanging state.
2:
var request = $.ajax(
{
type: 'POST',
url: 'someurl',
success: function(result){}
});
then,
request.abort();
Not working. Too many requests are still in pending state.
I don't know what is wrong with it.
I tried using 'jquery-throttle-debounce'. Included file 'jquery.ba-throttle-debounce.min.js'
Applied jQuery.debounce to stop event for slider.
$("#slider").on( "slidestop", $.debounce( 240, slide_stop ) );
I tried by reducing time delay. But no expected result. Same case as above.
You can use a plugin such as http://archive.plugins.jquery.com/project/AjaxManager.
Also, as Javis mentioned you are best off to also incorporate a delay before sending the response so that you don't needlessly overload your server with requests. Instead of using time differences or arrays I would just keep a reference to the last ajax request created. You would abort that request each time a new one is made.
// just putting these here for example, you would want to associate them with each instance of your slider control.
var timeout, last_request;
function slide_stop(event, ui){
$('span#imgLoader').html('<img src="/assets/ajax-loader.gif">');
// clear the timeout so a previous request won't be sent
clearTimeout(timeout)
// set the request to be sent in .5 seconds
timeout = setTimeout(send_request, 500);
}
function send_request(){
// abort the last request if there is one
if(last_request){
last_request.abort();
}
last_request = $.ajax({
type: 'get',
dataType: 'json',
url: 'some_url',
data: { is_ajax: true }
}).done(function(response){
$('span#imgLoader').empty();
// set the last request to null so that you dont abort a completed request
//(this might not be necessary)
last_request = null;
});
}
Try to throttle those requests, so many request to server can cause a performance issue on your server side, because remember that each ajax call is an apache (or whatever your webserver is) request, which costs memory and cpu. And remember that excesive requests can cause a DDoS.
Remember that as an user, i can start playing with the slider so much as i like, causing a ton of requests.
You should add something like:
var requests = [];
var lastCall = (+new Date()); // parenthesis just in case of minify
var timeDiff = 1000;
function sliderStopCallback() { // this will be called each time the slider stop sliding
if(timeDiff < 400) {
return;
}
abortAll();
requests.push( $.ajax({
type: 'POST',
url: 'someurl',
success: function(result){}
}) );
}
function sliderStartCallback() { // this will be call each time the user start to slide
timeDiff = (+new Date()) - lastCall;
lastCall = (+new Date());
}
function abortAll() {
var l = requests.length;
while(l--) {
requests[l].abort && requests[l].abort(); // the if is for the first case mostly, where array is still empty, so no abort method exists.
}
}
That way, each request will be sent at least on 400 milliseconds, preventing calls every 2 seconds.
This should work, but please notice that i havent tested it, so if you're going to try it please remember that it is a general idea, not a precise solution.
Good luck.
As said in comments, it's best to not send the request until the slideStop event. This way requests are not sent every time the value is changed, and thus server load is much less of an issue.
function ajaxrequest(){
$.ajax(....);
}
$("#slider").on("slideStop", function() {
ajaxrequest();
});

Categories