How to correctly handle ajax timeouts - javascript

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);

Related

AJAX request every 1 second without blocking the webpage, how to approach it

I am making a web app that will control a device that I have. The device has its own webserver (of which I have no control of) and it has its own language.
The way that I am controlling it is by doing ajax GET requests that are executing scripts on the device.
For example "http://127.0.0.1/nameOfScript?varName=varValue
I can also get data from the device through JSON, for example http://127.0.0.1/GetValuesJSON and it sends back the json object that I have created, so far so good.
My problem is that I want to continuously read data from the device and be able to send data at the same time, so I need to run the second URL every 0.5/1second, and be able to run the first URL whenever I want, the device and the webpage will be on a local network, security is not of importance.
Is it possible to do this within the HTML/JavaScript, if so, how? What do I have to research? Or do I need to use an additional webserver for the webpage that will handle this?
#abhinavxeon
^ Gave a solution to the problem, someone downvoted him and the comment was deleted, but the solution works and does exactly what I want it to do!
Here is the solution:
var interval;
function doSomething() {
$.ajax({
type: 'GET',
url: 'someurl',
data: $(this).serialize(),
dataType: 'json',
success: function (data) {
$("#content").text(data)
interval = setTimeout(doSomething, 1000);
}
});
}
doSomething();
function yourfuncation() {
$.ajax({
type: 'GET',
url: 'GETURL',
success: function (data) {
$('#hidden').val(data);// first set the value
}
});
}
use setinterval
var interval = setInterval(yourfunction, 1000);
or you can go with this way
var interval;
function callAjax() {
$.ajax({
type: 'POST',
url: 'increment.php',
data: $(this).serialize(),
dataType: 'json',
success: function (data) {
$('#hidden').val(data);// first set the value
interval = setTimeout(callAjax, 1000);
}
});
}
callAjax();
So you need to poll the server. The way you normally would do it is with websockets. If you can not do websockets, then you have to rely on polling. To do that, you need to use setTimeout or setInterval.
Basic idea to poll a server would be
function grabData () {
fetch('/your/endpoint/')
.then((response) => {
if (!response.ok) {
throw new Error('Something bad happened: ' + response.statusText );
}
return response.json(); // or text() if it is not JSON
})
.then((myJSON) => {
console.log(myJSON)
window.setTimeout(grabData, 2000) // call again after a delay
.catch((error) => {
console.error('There was a problem', error);
});
}
And you can make another function that does the update and call that when you need to send data up.

Retry ajax request after timeout

I'm using a prefilter to redo the ajax request 2 times max, see code below.
However the problem is that the original fail() handler of the ajax request is also called. This needs to be disabled of course.
$.ajaxPrefilter(function(options, originalOptions, jqXHR) {
// retry not set or less than 2 : retry not requested
if (!originalOptions.retryMax || !originalOptions.retryMax >= 2) {
return;
}
// no timeout was setup
if (!originalOptions.timeout > 0) {
return;
}
if (originalOptions.retryCount) {
originalOptions.retryCount++;
} else {
originalOptions.retryCount = 1;
// save the original error callback for later
if (originalOptions.error) {
originalOptions._error = originalOptions.error;
}
};
// overwrite *current request* error callback
options.error = $.noop();
// setup our own deferred object to also support promises that are only invoked
// once all of the retry attempts have been exhausted
var dfd = $.Deferred();
jqXHR.done(dfd.resolve);
// if the request fails, do something else yet still resolve
jqXHR.fail(function() {
var args = Array.prototype.slice.call(arguments);
if (originalOptions.retryCount >= originalOptions.retryMax || jqXHR.statusText !== "timeout") {
// add our _error callback to our promise object
if (originalOptions._error) {
dfd.fail(originalOptions._error);
}
dfd.rejectWith(jqXHR, args);
} else {
$.ajax(originalOptions).then(dfd.resolve, dfd.reject);
}
});
});
My request is: And i get the console.log message "we are in fail" at the same time as the request is redone for the first time. Any idea how to fix this?
$.ajax({
url: url,
crossDomain: true,
dataType: "json",
type: type,
timeout: 20000,
async: (async === undefined ? true : async),
beforeSend: beforeSend,
retryMax: (type == "POST" ? 0 : 2),
data: data
}).done(function(response, status, xhr) {
}).fail(function(xhr, textStatus, error) {
console.log("WE ARE IN FAIL");
});
easier way (sorry I only have the time to write partial code) :(
Create a recursive function that handle the ajax request and takes parameters + a counter.
var MyFuncAjax = function(params, counter){
if(counter <= 0){ return true; }
$ajax({
...
timeout: params.timeout
...
})
...fail(function(xhr...){
MyFuncAjax(params, --counter)
})
}
Then call it
MyFuncAjax({timeout: 20000, ....}, 2)
And voila :)

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();
});

Form Submission vs AJAX Polling Call

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);
}
});
}

Save temporary Ajax parameters in jQuery

I am developing a heavily scripted Web application and am now doing some Error handling. But to do that, I need a way to access the AJAX parameters that were given to jQuery for that specific AJAX Request. I haven't found anything on it at jquery.com so I am asking you folks if you have any idea how to accomplish that.
Here is an example of how I want to do that codewise:
function add_recording(filename) {
updateCounter('addRecording','up');
jQuery.ajax({
url: '/cgi-bin/apps/ajax/Storyboard',
type: 'POST',
dataType: 'json',
data: {
sid: sid,
story: story,
screen_id: screen_id,
mode: 'add_record',
file_name: filename
},
success: function(json) {
updateCounter('addRecording','down');
id = json[0].id;
create_record(id, 1, 1, json);
},
error: function() {
updateCounter('addRecording','error',hereBeData);
}
})
}
hereBeData would be the needed data (like the url, type, dataType and the actual data).
updateCounter is a function which updates the Status Area with new info. It's also the area where the User is notified of an Error and where a Dismiss and Retry Button would be generated, based on the Info that was gathered in hereBeData.
Regardless of calling complete() success() or error() - this will equal the object passed to $.ajax() although the values for URL and data will not always be exactly the same - it will convert paramerters and edit the object around a bit. You can add a custom key to the object to remember your stuff though:
$.ajax({
url: '/',
data: {test:'test'},
// we make a little 'extra copy' here in case we need it later in an event
remember: {url:'/', data:{test:'test'}},
error: function() {
alert(this.remember.data.test + ': error');
},
success: function() {
alert(this.remember.data.test + ': success');
},
complete: function() {
alert(this.remember.data.url + ': complete');
}
});
Of course - since you are setting this data originally from some source - you could rely on the variable scoping to keep it around for you:
$("someelement").click(function() {
var theURL = $(this).attr('href');
var theData = { text: $(this).text(); }
$.ajax({
url: theUrl,
data: theData,
error: function() {
alert('There was an error loading '+theURL);
}
});
// but look out for situations like this:
theURL = 'something else';
});
Check out what parameters you can get in the callback for error.
function (XMLHttpRequest, textStatus, errorThrown) {
// typically only one of textStatus or errorThrown
// will have info
this; // the options for this ajax request
}
You can use the ajax complete event which passes you the ajaxOptions that were used for the request. The complete fires for both a successful and failed request.
complete : function (event, XMLHttpRequest, ajaxOptions) {
//store ajaxOptions here
//1 way is to use the .data on the body for example
$('body').data('myLastAjaxRequest', ajaxOptions);
}
You can then retireve the options using
var ajaxOptions = $('body').data('myLastAjaxRequest');

Categories