I have the following query in jquery. It is reading the "publish" address of an Nginx subscribe/publish pair set up using Nginx's long polling module.
function requestNextBroadcast() {
// never stops - every reply triggers next.
// and silent errors restart via long timeout.
getxhr = $.ajax({
url: "/activity",
// dataType: 'json',
data: "id="+channel,
timeout: 46000, // must be longer than max heartbeat to only trigger after silent error.
error: function(jqXHR, textStatus, errorThrown) {
alert("Background failed "+textStatus); // should never happen
getxhr.abort();
requestNextBroadcast(); // try again
},
success: function(reply, textStatus, jqXHR) {
handleRequest(reply); // this is the normal result.
requestNextBroadcast();
}
});
}
The code is part of a chat room. Every message sent is replied to with a null rply (with 200/OK) reply, but the data is published. This is the code to read the subscribe address as the data comes back.
Using a timeout all people in the chatroom are sending a simple message every 30 to 40 seconds, even if they don't type anything, so there is pleanty of data for this code to read - at least 2 and possibly more messages per 40 seconds.
The code is 100% rock solid in EI and Firefox. But one read in about 5 fails in Chrome.
When Chrome fails it is with the 46 seconds timeout.
The log shows one /activity network request outstanding at any one time.
I've been crawling over this code for 3 days now, trying various idea. And every time IE and Firefox work fine and Chrome fails.
One suggestion I have seen is to make the call syncronous - but that is clearly impossible because it would lock up te user interface for too long.
Edit - I have a partial solution: The code is now this
function requestNextBroadcast() {
// never stops - every reply triggers next.
// and silent errors restart via long timeout.
getxhr = jQuery.ajax({
url: "/activity",
// dataType: 'json',
data: "id="+channel,
timeout: <?php echo $delay; ?>,
error: function(jqXHR, textStatus, errorThrown) {
window.status="GET error "+textStatus;
setTimeout(requestNextBroadcast,20); // try again
},
success: function(reply, textStatus, jqXHR) {
handleRequest(reply); // this is the normal result.
setTimeout(requestNextBroadcast,20);
}
});
}
Result is sometimes the reply is delayed until the $delay (15000) happens, Then the queued messages arrive too quicly to follow. I have been unable to make it drop messages (only tested with netwrok optomisation off) with this new arrangement.
I very much doubt that delays are dur to networking problems - all machines are VMs within my one real machine, and there are no other users of my local LAN.
Edit 2 (Friday 2:30 BST) - Changed the code to use promises - and the POST of actions started to show the same symptoms, but the receive side started to work fine! (????!!!???).
This is the POST routine - it is handling a sequence of requests, to ensure only one at a time is outstanding.
function issuePostNow() {
// reset heartbeat to dropout to send setTyping(false) in 30 to 40 seconds.
clearTimeout(dropoutat);
dropoutat = setTimeout(function() {sendTyping(false);},
30000 + 10000*Math.random());
// and do send
var url = "handlechat.php?";
if (postQueue.length > 0) {
postData = postQueue[0];
var postxhr = jQuery.ajax({
type: 'POST',
url: url,
data: postData,
timeout: 5000
})
postxhr.done(function(txt){
postQueue.shift(); // remove this task
if ((txt != null) && (txt.length > 0)) {
alert("Error: unexpected post reply of: "+txt)
}
issuePostNow();
});
postxhr.fail(function(){
alert(window.status="POST error "+postxhr.statusText);
issuePostNow();
});
}
}
About one action in 8 the call to handlechat.php will timeout and the alert appears. Once the alert has been OKed, all queued up messages arrive.
And I also noticed that the handlechat call was stalled before it wrote the message that others would see. I'm wondering if it could be some strange handling of session data by php. I know it carefully queues up calls so that session data is not corrupted, so I have been careful to use different browsers or different machines. There are only 2 php worker threads however php is NOT used in the handling of /activity or in the serving of static content.
I have also thought it might be a shortage of nginx workers or php processors, so I have raised those. It is now more difficult to get things to fail - but still possible. My guess is the /activity call now fails one in 30 times, and does not drop messages at all.
And thanks guys for your input.
Summary of findings.
1) It is a bug in Chrome that has been in the code for a while.
2) With luck the bug can be made to appear as a POST that is not sent, and, when it times out it leaves Chrome in such a state that a repeat POST will succeed.
3) The variable used to store the return from $.ajax() can be local or global. The new (promises) and the old format calls both trigger the bug.
4) I have not found a work around or way to avoid the bug.
Ian
I had a very similar issue with Chrome. I am making an Ajax call in order to get the time from a server every second. Obviously the Ajax call must be asynchronous because it will freeze up the interface on a timeout if it's not. But once one of the Ajax calls is a failure, each subsequent one is as well. I first tried setting a timeout to be 100ms and that worked well in IE and FF, but not in Chrome. My best solution was setting the type to POST and that solved the bug with chrome for me:
setInterval(function(){
$.ajax({
url: 'getTime.php',
type: 'POST',
async: true,
timeout: 100,
success: function() { console.log("success"); },
error: function() { console.log("error"); }
});
}, 1000);
Update:
I believe the actual underlying problem here is Chrome's way of caching. It seems that when one request fails, that failure is cached, and therefore subsequent requests are never made because Chrome will get the cached failure before initiating subsequent requests. This can be seen if you go to Chrome's developer tools and go to the Network tab and examine each request being made. Before a failure, ajax requests to getTime.php are made every second, but after 1 failure, subsequent requests are never initiated. Therefore, the following solution worked for me:
setInterval(function(){
$.ajax({
url: 'getTime.php',
cache: false,
async: true,
timeout: 100,
success: function() { console.log("success"); },
error: function() { console.log("error"); }
});
}, 1000);
The change here, is I am disabling caching to this Ajax query, but in order to do so, the type option must be either GET or HEAD, that's why I removed 'type: 'POST'' (GET is default).
try moving your polling function into a webworker to prevent freezing up in chrome.
Otherwise you could try using athe ajax .done() of the jquery object. that one always works for me in chrome.
I feel like getxhr should be prefixed with "var". Don't you want a completely separate & new request each time rather than overwriting the old one in the middle of success/failure handling? Could explain why the behavior "improves" when you add the setTimeout. I could also be missing something ;)
Comments won't format code, so reposting as a 2nd answer:
I think Michael Dibbets is on to something with $.ajax.done -- the Deferred pattern pushes processing to the next turn of the event loop, which I think is the behavior that's needed here. see: http://www.bitstorm.org/weblog/2012-1/Deferred_and_promise_in_jQuery.html or http://joseoncode.com/2011/09/26/a-walkthrough-jquery-deferred-and-promise/
I'd try something like:
function requestNextBroadcast() {
// never stops - every reply triggers next.
// and silent errors restart via long timeout.
getxhr = jQuery.ajax({
url: "/activity",
// dataType: 'json',
data: "id="+channel,
timeout: <?php echo $delay; ?>
});
getxhr.done(function(reply){
handleRequest(reply);
});
getxhr.fail(function(e){
window.status="GET error " + e;
});
getxhr.always(function(){
requestNextBroadcast();
});
Note: I'm having a hard time finding documentation on the callback arguments for Promise.done & Promise.fail :(
Perhaps it can be worked around by changing the push module settings (there are a few) - Could you please post these?
From the top of my head:
setting it to interval poll, would kinda uglily solve it
the concurrency settings might have some effect
message storage might be used to avoid missing data
I would also use something like Charles to see what exactly does happen on the network/application layers
Related
I have read many similar questions to mine from long ago but have yet to find the answer to my problem, so apologies if it sounds so familiar.
I have a Laravel/PHP web app which loads in an excel file of transactions. These are processed as either success or failure. In development it takes about two seconds per transaction. A typical file has about 40 transactions. I am now wanting to use the Bootstrap progress bar to provide feedback to the user about how far along the processing is going.
I have a page with a button to fire the import, previously file selection and things have happened, so I can just call the backend URL (audit.import) with the correct parameters and the upload will happen and work. So what I have done, is create a URL to return the status of the upload from the server (loadprogress). The plan being that the loadprogress will be called via ajax and the magic of js setTimeout, in order to poll the backend. Once we see all records have been successful or not, then the poll can end.
The problem is, the loadprogress poll fires regularly, right up until I press the button to start the main file load. Then it fails to fire again until the main file load has completed, thus removing the planned use for the progress meter.
My javascript looks like this,
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
<script type="text/javascript">
$(document).ready(function() {
var fullname = '<?php echo $fullname; ?>';
$("#ajaxButton").click(function(event) {
$.ajaxSetup({
headers: {
'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content')
}
});
$.ajax({
url: '/audit.import/1/' + fullname,
type: 'POST',
async: true,
}).always(function(xhr, status) {
console.log("Import complete with status of " + status);
});
console.log("sent async call to perform audit");
});
(function loadProgress() {
$.ajax({
type: 'GET',
url: '/loadprogress',
}).done(function(result) {
console.log(result);
}).then(function() {
setTimeout(loadProgress, 100);
})
})();
});
</script>
I am on a mac in safari, but have tried chrome on mac with the same results.
Any assistance would be welcomed.
Thanks.
It was indeed a server side problem. I was running on a single threaded test server which means there was no second thread for my second process. Hence the block. By default the test Laravel server that comes with the install is only single threaded. A fact I had missed in my setup of the test server.
I have looked at the following thread
jQuery Ajax - Status Code 0?
However I could not find a definitive answer and I am having serious trouble trying to find the source of my issue so I am posting here in the hopes that someone can point me in the right direction.
In my code I am performing an Angular HTTP post which just sends basic JSON data, then within the on success callback I am using AJAX to upload files to the same server. (I know I should not be using jQuery and Angular however I can't change this for the moment)
It looks something like this
var deferred = $q.defer()
// first post
$http.post(url,payload,{params: params, headers: headers)
.then(function(response) {
uploadFiles(response,deferred);
// I am also sending google analytics events here
}, function(error) {
// do error stuff
}
return deferred.promise;
// upload files function
function uploadFiles(response,deferred){
$ajax({
type: 'POST',
processData: false,
contentType: false,
data: data // this new FormData() with files appended to it,
url: 'the-endpoint-for-the-upload',
dataType: 'json',
success: function(data) {
// do success stuff here
deferred.resolve(data);
},
error: function(jqXHR, textStatus, errorThrown) {
var message = {};
if (jqXHR.status === 0) {
message.jqXHRStatusIsZero = "true";
}
if (jqXHR.readyState === 0) {
message.jqXHRReadyStateIsZero = "true";
}
if (jqXHR.status === '') {
message.jqXHRStatusIsEmptyString = "true";
}
if (jqXHR.status) {
message.jqXHRStatus = jqXHR.status;
}
if (jqXHR.readyState) {
message.jqXHRReadyState = jqXHR.readyState;
}
if (jqXHR.responseText) {
message.jqXHR = jqXHR.responseText;
}
if (textStatus) {
message.textStatus = textStatus;
}
if (errorThrown) {
message.errorThrown = errorThrown;
}
message.error = 'HTTP file upload failed';
logError(message);
deferred.resolve(message);
}
}
})
}
Not my exact code but almost the exact same.
The issue is that is works almost all of the time, but maybe three or four in every few hundred will fail. By fail I mean the error handler function is called on the file upload function and the files are not uploaded.
I get jqXHRStatus 0 and jqXHRReadyState 0 when this occurs.
The only way I am able to replicate the issue is by hitting the refresh on the browser when the request is being processed, however users have advised they are not doing this (although have to 100% confirm this)
Is there perhaps a serious flaw in my code which I am not seeing? Maybe passing deferred variable around isn't good practice? Or another way the ajax request is being cancelled that I am not considering? Could sending google analytics events at the same time be interfering?
Any advice would be great and please let me know if you would like more information on the issue.
This means, the request has been canceled.
There could be many different reasons for that, but be aware: this could be also due to a browser bug or issue - so i believe (IMHO) there is no way to prevent this kind of issues.
Think for example, you get a 503 (Service Unavailable) response. What you would do in such a case? This is also a sporadic and not predictable issue. Just live with that, and try to repost your data.
Without reinventing the wheel, I suggest you to implement:
Retrying ajax calls using the deferred api
My guess is that your code is executing before it actually gets back from the call. I.e. the call goes back and nothing was returned and it gives a 0 error. This would make sense as the error is variable. Most of the time it would return fine because the backend executed fast enough but sometimes it wouldn't because it took especially long or something else happened etc. Javascript doesn't ever REALLY stop execution. It says it does but especially passing between angular and jquery with multiple ajax requests it wouldn't be surprising if it was executing the second ajax call before it actually completed your angular post. That's why a refresh would replicate the error because it's would clear your variables.
Some things you can do to test this:
On the backend make a timer that goes for a few seconds before it returns anything. This will probably make your code fail more consistently.
Set breakpoints and see when they are being hit and the values they contain in the javascript.
Good luck!
I know we can make a javascript ajax request from some server and it either receives the response or gives timeout error after some time.
Let's consider this scenario when we don't want to wait for the request rather the server would send a response(or we can say it would be another request from server to client) async at any time after getting the request and then call a javascript CB function with the response.
I am looking for ideas for how to go about it mainly supporting all modern browsers and if possible not relying on any 3rd party plugin except may be jQuery.
The main feature of Ajax is that it IS asynchronous by default, and your program will continue to run without waiting for the response. So unless I'm misreading your question, it is what you need.
If you use jquery, then you pass in a callback function that will execute only when the server sends back a response. You can specify a timeout in the settings, though I'm not sure what the maximum time you can provide without getting a timeout error. But it will be several seconds, at least.
You can even specify different callbacks for success and fail as follows (adapted from the jquery ajax API, but added a timeout of 5 seconds):
var request = $.ajax({
url: "http://www.some.url/",
method: "GET",
data: { some : stuff },
dataType: "html",
timeout: 5000
});
request.done(function( data ) {
console.log( "SUCCESS: " + data );
});
request.fail(function() {
console.log( "Request failed");
});
I came across this question after 4 years. I dont remember in what context I asked this but for anyone who has the same query:
Http is a request/response protocol. Which means the client sends a request and the server responds to that request with some message/data. Thats the end of the story for that request.
In order for the server to trigger something on the clientside we will have to use something that keeps the connection to the server rather than ending the communication after getting the response. Socket.io is bi directional event driven library that solves this problem.
To update a cart (PHP Session storage and reserve the stock of items in database) on my online shop, I simply add a timeout of 100ms after calling it and remove Success/Error callback.
$.ajax({
url: 'http://www.some.url/',
method: 'GET',
data: {
some : 'stuff'
},
dataType: 'html',
timeout: 100
});
Note : It doesn't matter if some requests didn't arrive, because when the order is saved, an update of the whole cart is sent with a callback.
If your query needs acknowledge, don't use that solution !
I believe your question is similar to this
by Paul Tomblin. I use the answer provided by gdoron, which is also marked as the best solution, and also the comment by AS7K.
$.ajax({
url: "theURL",
data: theData
});
NB: No async parameter provided.
It seems to be possible to set a timeout value when doing an Ajax request in plain javascript. see How to detect timeout on an AJAX (XmlHttpRequest) call in the browser?
It is also possible when using jQuery's ajax implementation, & other similar frameworks I assume. see Set timeout for ajax (jQuery)
Browsers seem to have rather vague specification regarding their default timeout. see Browser Timeouts
Hence one might "hey, I'm going to set a timeout to my ajax request so all the users will have the same timeout".
But then, the next question follow: would it actually override the browser's timeout in all cases?
When I say "all" cases, I mean for instance, if the browser timeout value is smaller than your ajax request timeout value.
I suspect it does not.
And I also suspect it is best practice to always have a timeout error handler to make sure that whatever happens you can display a relevant message that will save hours of work to your support team, & money to your company. see Determine if $.ajax error is a timeout
Thanks in advance
It is an interesting question, I made some experiments in Chrome 59.0 and Firefox 54.0 using a 10min delay service as the backend.
After some test setting the timeout on the client to 10 minutes I had an error response with text status "error" after 300 seconds (5 minutes) in both browsers, so at least for these two browsers it is not possible to override the internal timeout value. I am assuming the same behavior for the remaining browsers in the market.
My test script: (similar results for vanilla JavaScript)
var st = new Date();
$.ajax({
url: "https//mysitewith10minresponse.com/foobar",
type: "GET",
dataType: "json",
timeout: 600000,
success: function(response) { console.log(response); },
error: function(jqXHR, textStatus, errorThrown) {
st = (new Date() - st)/1000;
alert("Text Status " + textStatus + ", diff: " + st + " seconds");
}
});
This question might seem a bit odd, the problem arised when the page went through webtests.
The page uses an AJAX call (async set to true) to gather some data. For some reason it won't swap pages before the AJAX call has returned - consider the following code:
console.log("firing ajax call");
$.ajax({
type: "POST",
url: "requestedService",
data: mode : "requestedMethod",
cache: false,
dataType: "json",
success: function() { console.log("ajax response received") },
error: null,
complete: null,
});
console.log("changing window location");
window.location = "http://www.google.com"
The location only changes after AJAX returns the response. I have tested the call, it is in fact asynchronous, the page isn't blocked. It should just load the new page even if the AJAX call hasn't completed, but doesn't. I can see the page is trying to load, but it only happens once I get the response. Any ideas?
The console output is:
firing ajax call
changing window location
ajax response received
This seems to work fine for me. The location is changed before the code in the async handler executes. Maybe you should post some real code and not a simplified version, so that we can help better.
Here is a demonstration that works as you expect: http://jsfiddle.net/BSg9P/
$(document).ready(function() {
var result;
$("#btn").on('click', function(sender, args) {
setInterval(function() {
result = "some result";
console.log("Just returned a result");
}, 5000);
window.location = "http://www.google.com";
});
});
And here is a screenshot of the result: http://screencast.com/t/VbxMCxxyIbB
I have clicked the button 2 times, and you can see in the JS console that the message about the location change is printed before the result each time. (The error is related to CORS, if it was the same domain, it would navigate).
Bit late but maybe someone else will have the same issue.
This answer by #todd-menier might help: https://stackoverflow.com/questions/941889#answer-970843
So the issue might be server-side. For eg, if you're using PHP sessions by default the user's session will be locked while the server is processing the ajax request, so the next request to the new page won't be able to be processed by the server until the ajax has completed and released the lock. You can release the lock early if your ajax processing code doesn't need it so the next page load can happen simultaneously.