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 :)
Related
I'm uploading a list of documents to the server, and for each document I launch an ajax request, but the number of requests is unknown (Depends on the number of document being uploaded). How can I show a message to the user when all the documents are uploaded (All ajax requests are done).
$.each(files,function(idx,elm){
let formData = new FormData();
let ID = '_' + Math.random().toString(36).substr(2, 9);
formData.append('document_id', ID);
formData.append('file-doc', elm);
$.ajax({
url: "http://127.0.0.1:5000/add_multiple_docs",
type: 'POST',
data: formData,
cache: false,
contentType: false,
processData: false,
beforeSend: function (xhr, settings) {
function getCookie(name) {
var cookieValue = null;
if (document.cookie && document.cookie !== '') {
var cookies = document.cookie.split(';');
for (var i = 0; i < cookies.length; i++) {
var cookie = jQuery.trim(cookies[i]);
// Does this cookie string begin with the name we want?
if (cookie.substring(0, name.length + 1) === (name + '=')) {
cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
break;
}
}
}
return cookieValue;
}
if (!(/^http:.*/.test(settings.url) || /^https:.*/.test(settings.url))) {
// Only send the token to relative URLs i.e. locally.
xhr.setRequestHeader("X-CSRFToken", getCookie('csrftoken'));
}
},
success: function (data) {
alert(data);
},
failure: function (request) {
console.log(request);
},
error: function (jqXHR, exception) {
console.log("error add document");
let msg = '';
if (jqXHR.status === 0) {
msg = 'Not connect.\n Verify Network.';
} else if (jqXHR.status === 404) {
msg = 'Requested page not found. [404]';
} else if (jqXHR.status === 500) {
msg = 'Internal Server Error [500].';
} else if (exception === 'parsererror') {
msg = 'Requested JSON parse failed.';
} else if (exception === 'timeout') {
msg = 'Time out error.';
} else if (exception === 'abort') {
msg = 'Ajax request aborted.';
} else {
msg = 'Uncaught Error.\n' + jqXHR.responseText;
}
console.log(msg)
}
});
});
}
First of all, you know how many files are uploaded, so you know how many ajax request you are doing. (1 Request per file)
So before your $.each() fires you get the size of files
let count = $(files).size();
$.each(files,function(idx,elm){
/*your code with requests*/
}
Now, after each ajax request hast fired, decrement count. Decrement inside your success and failure methods, because it doesn't mattet if it succeeded or not. And check if count === 0. If it's 0 than you know all ajax are done.
$.ajax({
/*your other settings*/
success: function (data) {
alert(data);
count--;
doSomething(count);
},
failure: function (request) {
console.log(request);
count--;
doSomething(count);
},
});
function doSomething(count){
if(count === 0){
/*stuff you wannna do after all ajax requests are done*/
}
}
I haven't done that many ajax for now, so I'm not quite sure if failure is also fired on error, but if not maybe add count-- and the if on error as well.
To achieve what you need you can place all the jqXHR objects returned from $.ajax() in an array which you can apply() to $.when(). Then you can execute whatever logic you require after all of those promises have been resolved. Try this:
var promises = files.map(function(elm) {
// setup formData...
return $.ajax({
url: "http://127.0.0.1:5000/add_multiple_docs",
// ajax settings...
});
});
$.when.apply($, promises).done(function() {
console.log('all requests complete, do something here...');
});
However, it's definitely worth noting that sending AJAX requests in a loop is not a scalable pattern to use. It would be a much better idea to aggregate all the file and related data in a single AJAX request and handle that once on the server.
A very interesting question.
I had a similar issue when trying to get data from the youtube API which only returned a max result set of 50 items.
To solve this problem I used a recursive function that accepted a callback, on base case (in my case when there was no nextPageToken) I called the callback function.
The recursion was triggered in the success handler of the $.ajax request.
function fetchVideos(nextPageToken, callback) {
if (nextPageToken === null || nextPageToken === undefined) {
callback(null);
return;
}
$.ajax(requestURI + `&pageToken=${nextPageToken}`, {
success: (data) => {
// use data somehow?
fetchVideos(data.nextPageToken, callback);
},
error: (err) => {
callback(err);
}
})
}
fetchVideos("", (err) => {
if (err) {
console.log(err.responseJSON);
return;
}
updateUI();
})
When there was no longer a nextPageToken in the response it would trigger the if statement. I think it works kind of sync? Because I need the nextPageToken to perform the next request.
I know this is not the best case for you situation, but I answered based on the title which is how I came to this page :)
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);
I have written a custom js module that basically sends messages and needs to wait for a response in order to continue:
var manageBooking = (function (jQ) {
//decalre private variables
var domain, msgRecieved, msgResponse, validationValue;
//decalre private functions
var sendMessage, wait;
// A private variables
domain = document.domain;
msgRecieved = false;
msgResponse = null;
wait = function(timeOutStep){
var w;
console.log('msgRecieved', msgRecieved);
if (msgRecieved === true) {
clearTimeout(w);
return;
} else {
console.log('waiting..');
w = setTimeout(wait, timeOutStep, timeOutStep);
}
}
// A private function to send messages
sendMessage = function( requestURL, data, type ) {
console.log(requestURL);
console.log(data);
console.log(type);
//reset vars to defaults
msgRecieved = false;
msgResponse = null;
jQuery.ajax({
url: "http://"+domain+"/_ajax/"+requestURL,
dataType: "html",
async: true,
data: data,
type: type,
success: function(msg){
console.log(msg);
msgResponse = msg;
msgRecieved = true;
}
});
console.log('after ajax call');
wait(500);
console.log('after wait');
console.log('msgRecieved', msgRecieved);
return;
};
return {
// A public variable
errorMsg: "",
validationName: "",
bookingID: "",
output: "",
// A public function to login
login: function( enteredBookingID, enteredSurname ) {
// Call private sendMsg
sendMessage("user_login/"+enteredBookingID+"/"+enteredSurname, null, 'GET');
console.log(msgResponse);
throw "error";
//check response
var patt=/Sorry/i;
//test pattern
var result=patt.test($.trim(msgResponse));
//if false OK
if (result === false) {
var split = msgResponse.split('|');
validationName = split[0];
validationValue = split[1];
bookingID = enteredBookingID
return true;
}
//else error
errorMsg = msgResponse;
return false;
}
};
})(jQuery);
manageBooking.login(123,123);
The issue i am having is forcing the sendMessage function to wait until the ajax completes and sets msgRecieved to true.
However it appears that the sendMessage function hits the wait function once and then continues.
the following console output shows the order of events:
GET http://website/_ajax/user_login/123/123
after ajax call //sendMessage()
msgRecieved, false //wait()
waiting.. //wait()
after wait //sendMessage()
msgRecieved, false //sendMessage()
null//login()
uncaught exception: error //login()
<p>Sorry, we cannot locate your details. </p> <!-- jQuery Ajax call -->
msgRecieved, true //wait()
What I am confused with is that the wait function seems to fire again right at the end..
can anyone give me some pointers on getting this to work?
JavaScript behaves in an asynchronous manner, meaning it does not wait.
You have a part in your code that looks like this:
jQuery.ajax({
url: "http://"+domain+"/_ajax/"+requestURL,
dataType: "html",
async: true,
data: data,
type: type,
success: function(msg){
console.log(msg);
msgResponse = msg;
msgRecieved = true;
}
});
You should place the code to be run when the response arrives within the success function, like so:
success : function (msg) {
handleMessage(msg); // Or any other manipulation to the received message
}
function handleMessage(msg) {
// Work with your received message here.
}
success will be called with the received message, it is a callback.
The right way to implement sendMessage would be the following way:
sendMessage = function( requestURL, data, type, callback ) {
console.log(requestURL);
console.log(data);
console.log(type);
//reset vars to defaults
msgRecieved = false;
msgResponse = null;
jQuery.ajax({
url: "http://"+domain+"/_ajax/"+requestURL,
dataType: "html",
async: true,
data: data,
type: type,
success: function(msg){
console.log(msg);
msgResponse = msg;
msgRecieved = true;
// Call the callback function to notify the message
// was received
callback();
}
});
};
and then using it like so:
sendMessage(urlHere, dataHere, typeHere, function () {
// Message has been received, msgResponse and msgReceived
// have already been updated. Do what you need here
});
the problem may be related to the scope of your w variable, because on second call (in the wait function, into your else branch) you're destroying the reference to the timeout you previously created, so the clearTimeout can't work: try to define it in the immediate outer scope.
You should try using the JavaScript setInterval function instead of setTimeout. But this time, break up sendMessage, and place the part that needs to execute after the ajax message is received under a setInterval.
Once the ajax message is received this second part of sendMessage runs (after messagereceived is true), and also clears the interval.
This is since setTimeout() only executes once after a set interval.
setInterval() executes repeatedly exery interval until it is cleared.
More information can be found Here
Hope this helps!
I have the following code which is giving me a Method POST, Status (canceled) error message:
$(document).ready(function() {
var xhr = false;
get_default();
$('#txt1').keyup( function() {
if(xhr && xhr.readyState != 4){
alert("abort");
xhr.abort();
}
if ($("#txt1").val().length >= 2) {
get_data( $("#txt1").val() );
} else {
get_default();
}
});
function get_data( phrase ) {
xhr = $.ajax({
type: 'POST',
url: 'http://intranet/webservices.asmx/GetData',
data: '{phrase: "' + phrase + '"}',
contentType: 'application/json; charset=utf-8',
dataType: 'json',
success: function( results ) {
$("#div1").empty();
if( results.d[0] ) {
$.each( results.d, function( index, result ) {
$("#div1").append( result.Col1 + ' ' + result.Col2 + '<br />' );
});
} else {
alert( "no data available message goes here" );
}
},
error: function(xhr, status, error) {
var err = eval("(" + xhr.responseText + ")");
alert(err.Message) ;
}
});
}
function get_default() {
$('#div1').empty().append("default content goes here.");
}
});
The code actually works as long as each ajax request completes, but if I type fast into txt1, i.e. type the next character before the previous request finishes, I get the error message Method POST, Status (canceled).
Anyone know why this is happening and how to correct the error?
I suppose that the problem is very easy. If you call xhr.abort(); then the error callback of $.ajax will be called for the pending request. So you should just ignore such case inside of error callback. So the error handler can be modified to
error: function(jqXHR, textStatus, errorThrown) {
var err;
if (textStatus !== "abort" && errorThrown !== "abort") {
try {
err = $.parseJSON(jqXHR.responseText);
alert(err.Message);
} catch(e) {
alert("ERROR:\n" + jqXHR.responseText);
}
}
// aborted requests should be just ignored and no error message be displayed
}
P.S. Probably another my old answer on the close problem could also interesting for you.
That is because you are calling abort method which possibly triggers the error handler with appropriate error message.
You can possibly wait for previous ajax request to complete before making the next call.
In order to both fix your problem and save on the amount of Ajax calls I have written the following example. This example allows you to handle the following two situations:
Situation 1:
The user types slow enough (lets say about one key every 200+ miliseconds
Situation 2:
The user types fast (my average is about 20 to 50 miliseconds per key)
In the following example there is no need to abort or ignore Ajax calls, you are not spamming Ajax calls and you are using an Object to handle your job. (I even jsFiddled it for you)
var Handler = {
/**
* Time in ms from the last event
*/
lastEvent: 0,
/**
* The last keystroke must be at least this amount of ms ago
* to allow our ajax call to run
*/
cooldownPeriod: 200,
/**
* This is our timer
*/
timer: null,
/**
* This should run when the keyup event is triggered
*/
up: function( event )
{
var d = new Date(),
now = d.getTime();
if( ( now - Handler.lastEvent ) < Handler.cooldownPeriod ) {
// We do not want to run the Ajax call
// We (re)set our timer
Handler.setTimer();
} else {
// We do not care about our timer and just do the Ajax call
Handler.resetTimer();
Handler.ajaxCall();
}
Handler.lastEvent = now;
},
/**
* Function for setting our timer
*/
setTimer: function()
{
this.resetTimer();
this.timer = setTimeout( function(){ Handler.ajaxCall() }, this.cooldownPeriod );
},
/**
* Function for resetting our timer
*/
resetTimer: function()
{
clearTimeout( this.timer );
},
/**
* The ajax call
*/
ajaxCall: function()
{
// do ajax call
}
};
jQuery( function(){
var field = jQuery( '#field' );
field.on( 'keyup', Handler.up );
});
Hope this helps.
You are using the keyup event, which seems to be the problem.
If anything at all, you need to wait after typing one character before taking action.
A better solution might be to follow the same strategy as the JQuery AutoComplete COmponent.
Ajax is an async type, its not recommonded that u to send request on every keyup event, try the...
async: false
in post method... it'll pause the subsequent posts until the current request done its callback
Realistically you need a setTimeout method in order to prevent redundant ajax calls being fired.
clearTimeout(timer);
if($("#txt1").val().length >= 2){
timer = setTimeout(function(){
get_data($("#txt1").val());
}, 400);
}else{
get_default();
}
This should eradicate your problem.
I have 3 ajax call in one function and checkAjaxCompletion which checks each ajax completion flag.
What the code below does is send multiple separate ajax calls and interval method checks completion flags to determine whether to proceed or keep interval. (I know clearInterval is not shown but the point is I want to use something other than interval)
Current code is:
function manyAjax() {
setInterval( function() { checkAjaxCompletion(); } , 200);
ajax1();
ajax2();
ajax3();
}
function ajax1() {
//send ajax request to server and if success set flag to 1. Default is 0. Error is 2.
}
function ajax2() {
//send ajax request to server and if success set flag to 1. Default is 0. Error is 2.
}
function ajax3() {
//send ajax request to server and if success set flag to 1. Default is 0. Error is 2.
}
function checkAjaxCompletion() {
if(ajax1_flag == 1 && ajax2_flag == 1 && ajax3_flag == 1) {
//everything went success, do some process
}
else if(ajax1_flag == 2 || ajax2_flag == 2 || ajax3_flag == 2) {
//some ajax failed, do some process
}
else {
//all ajax have not been completed so keep interval i.e. do nothing here
}
}
But I'm hesitating to depend on using interval function because calling it so often seem such waste of memory. There must be better way to do. I'm thinking if observer pattern can be applied here but would like to hear opinions.
It is observer-notifier, if you want to call it that - but each of your ajax calls will more than likely have a callback in javascript when they complete. Why not call checkAjaxCompletion() at the end of each of them, and do nothing if you're still waiting on others?
Dustin Diaz does a great job with this example.
function Observer() {
this.fns = [];
}
Observer.prototype = {
subscribe : function(fn) {
this.fns.push(fn);
},
unsubscribe : function(fn) {
this.fns = this.fns.filter(
function(el) {
if ( el !== fn ) {
return el;
}
}
);
},
fire : function(o, thisObj) {
var scope = thisObj || window;
this.fns.forEach(
function(el) {
el.call(scope, o);
}
);
}
};
The publisher:
var o = new Observer;
o.fire('here is my data');
The subscriber:
var fn = function() {
// my callback stuff
};
o.subscribe(fn);
To unsubscribe:
var fn = function() {
// my callback stuff
};
o.subscribe(fn);
// ajax callback
this.ajaxCallback = function(){
$.ajax({
type: "POST",
url: ajax.url,
data: {key: value},
async : !isAll,// false使用同步方式执行AJAX,true使用异步方式执行ajax
dataType: "json",
success: function(data){
if(data.status == 'successful'){
selfVal.parent().find('.msg').addClass('ok').html(msg.ok);
}else if(data.status == 'failed'){
checkRet = false;
selfVal.parent().find('.msg').removeClass('ok').html(msg.error);
}else{
checkRet = false;
}
return this;
}
});
}
return this;
Maybe you want to check your inputvalue callback ajax in your form;
You can view my website Demo, hope help you.
http://6yang.net/myjavascriptlib/regForm
Okay my idea was to make your own object that can handle sending an array of requests, keep a history of each request and do what i'm gonna call 'postProccessing' on each response, here is a probably very dodgy bit of code to hopefully demonstrate what I am thinking.
var Ajax = function() {
var request, callback, lst;
if (window.XMLHttpRequest) {
request = new XMLHttpRequest();
} else if (window.ActiveXObject) {
request = new ActiveXObject("Microsoft.XMLHTTP");
}
request.onreadystatechange = handleResponse;
this.history = [{}];
this.send = function(args) {
for (var i = 0; i < args.length; i++) {
if (args.url) {
request.open(args.type || 'GET', args.url);
}
request.send(args.data || null);
callback = args.callback;
lst++;
}
}
function handleResponse() {
var response = {
url: '',
success: true,
data: 'blah'
};
history.push(response);
if (postProccess()) {
callback();
}
}
function postProcess() {
if (this.history[lst].success) {
return true;
} else {
return false;
}
}
}