long-poll jQuery.ajax() fails to callback after phone sleeps? - javascript

My web app uses the 'long poll' method to keep up to date with the latest data from my server. The server only responds when it has new data, which can be many minutes apart. (It is a heating control system where you only see updates when room temperatures changes or somebody changes the settings).
var version = "0";
function updater() {
$.ajax({
type: "POST",
url: "/listen",
data: version,
success: function (data) {
version = handleUpdates(data);
updater();
},
error: function () {
setTimeout(updater, 1000);
}
});
}
It works fine on desktop browsers and on phones except in one case. I have found that on android phones with Chrome something odd happens after the phone has gone to sleep for more then about 10 minutes. The post request seems to be dropped, which I guess is reasonable since the phone is asleep. In the Chrome debugger's Network tab, the Status Text of the POST request says (canceled).
The problem is when I wake the phone up while the request is cancelled, neither the success() or error() function is called, and my web app never gets updated. $.ajax() has broken its promise to call me back.
The problem only happens on some devices. I have been able to do a few ad-hoc tests by borrowing devices off friends. So far I have only seen the problem on android phones. But not is the phone is connected to a charger. I have not seen it on any tablets, on apple devices or windows PCs.
I have tried adding a timeout to the ajax settings:
timeout: 120 * 1000,
This helps because the error() function is eventually called up to 2 minutes after the wake up. But I'd like the user to see updates within 1 or 2 seconds. I don't want to make the timeout so short because it would create unnecessary server traffic.
I have also tried detecting whether device is asleep by looking for lateness in a one second setInterval as described in Can any desktop browsers detect when the computer resumes from sleep?.
When I detect the wake up, I abort() the post and start another. This helps in most cases. But it turns out to be unreliable. Sometimes time events seem to keep ticking normally during sleep and the post request gets cancelled anyway. And it it does not feel like a reliable fix.
I am using latest version of jQuery: (2.1.2) and Chrome (47).

I not sure this will work or not, I cannot test it now but give it a try
$(window).focus(function() {
updater();
});

I've had problems in the past with JavaScript calls getting suspended when the phone goes to sleep. The solution I ended up with was to use window.setInterval() which seems to suspend, but come back to life when the phone is woken up.
So I would recommend setting an interval which cancels the call every so often and reinitiates it. This might help it survive through a phone sleep.
Something roughly like:
var myCall = $.ajax({...});
Window.setInterval (refreshCall(), 10000);
function refreshCall (){
myCall.abort ();
myCall = $.ajax({...});
}

How about a higher-level watcher function like this:
var restartTimer = null;
function updater(){
$.ajax({
type: "POST",
url: "/listen",
data: version,
success: function (data) {
version = handleUpdates(data);
clearTimeout(restartTimer);
updater();
},
error: function () {
clearTimeout(restartTimer);
setTimeout(updater, 1000);
}
});
}
// Kick it when the phone wakes up.
$(window).focus(function(){
restartTimer = setTimeout(function(){
initializeAll();
}, 6000);
updater();
});
You know that the $(window).focus will fire when the phone wakes up, so you try updater() as Almis suggests, but with a fail-safe timer. If updater fires (on laptops or iOS), the timer is canceled and all is well, but if updater is dead, the fail-safe timer fires in 6 seconds and reboots your entire app by calling initializeAll().

How about a setInterval, that stores the time it was called, then compares the last time it was called to the current time - if your interval is 10 seconds and the time passed since the last run was 5 minutes, you can assume you've just woken from sleep? Then abort the current ajax call, and restart it.

The best answer is "don't do that". You're having the server wait to respond while it tracks for changes at the server side. Just have the server respond and have the jQuery ping on an interval. You can include lastchanged or haschanged if you want to prevent actually refreshing when there's no status change, but if the server is doing the same work either way, just let it respond and wait for the next poll.
setInterval(function () {
$.post({"/listen", version, function (data) {
if(data.haschanged)
version = handleUpdates(data);
}).fail(function () {
// any error correction on failed call
});
}, 1000);

Related

Chrome requests taking much longer than other browser requests

One of the users of my website noticed a severe delay in loading times for a infinite scroll function which makes a GET request to my API. While testing it, it only seems to happen in Chrome and Opera, the other browsers provide a near seamless experience.
So after some testing, I decided to do some time logging (sorry I can't post inline images on here due to too little reputation):
These logs are from the same calls, and as you can see chrome takes about 3000ms while firefox is around 90ms. IE/Edge have the same performance as Firefox.
To further the mystery, the network tab seems to report the ms correctly (around 100ms with some exceptions, but nowhere near 3000ms):
Now for the actual code I'm calling:
loadMore() {
console.time('start load');
//some extra stuff here
console.timeEnd('start load');
console.time('state api call');
this.$APIService.getCards(this.deckId, this.page, this.searchInput, this.lastId)
.then(res => {
console.timeEnd('actual call');
console.timeEnd('state api call');
//some more stuff here
})
}
the "this.$APIService.getCards" call is just my apihandler:
getCards (deckId, page, searchInput,lastId) {
console.time('actual call');
return Api().get('cards?deckId=' + deckId + '&lastId='+lastId + '&search='+ searchInput)
},
where "Api()" is my axios object:
axios.create({
withCredentials: true,
baseURL: baseURL
})
Another thing to note is that my server only receives the request after the delay, so that would correspond with the network tabs stating it only takes around 100ms.
So, does anyone have any idea of why this might be happening? As you can see I literally have the time logs start right before the request and end them right upon receiving a result. Why would there be a delay before executing the call only in Chrome?
Thanks in advance!

What happens to setTimeout when the window is out of focus?

I have a situation where I need to reauthenticate a token on a cordova app before the authentication token expires. To do that I figured I'd set a timeout just before the auth token expires, to reauthenticate.
function authenticate() {
var token = ... get token
setTimeout(function() {
.. try to reauthenticate
}, token.expiresIn - 600*1000);
}
Problem I could see is that-
The timeout period passes while the app is sleeping. Function does not fire?
The timeout "countdown" (if that's how it works) is paused while the app is sleeping.
Neither of these are good scenarios. So my question is, what happens to a timeout while the application is out of focus? Should I instead have a 10 second interval that checks the expiration for this scenario?
Edit:
So lets say the token is for 4 hours. If the user uses the app for an hour, minimizes it for 2 hours and comes back, will the function call in an hour or 3 hours? This would be the point of the interval, so I can check the situation relatively quickly.
The timeout behavior really depends on the device type and OS version. On some, any timers that are "due" fire as soon as the application becomes active. On others (and I believe this is the case for current iOS), the timer is paused while your application is inactive and resumes when it becomes active.
For a long-running timer (i.e. your 4 hours example) you can't rely on the setTimeout() because on some devices it won't account for the inactive time. You'll need to subscribe to Cordova's resume event and re-calculate and update your timers. The following setLongTimeout() function should behave as expected in Cordoval. It's untested and would need to be expanded if you need multiple long timeouts.
var longTimeoutId, longTimeoutDate, longTimeoutCallback;
// Use instead of `setTimeout()` for a long timeout in Cordova
function setLongTimeout(callback, delay) {
if (longTimeoutId) {
clearTimeout(longTimeoutId);
}
longTimeoutCallback = callback;
longTimeoutDate = Date.now() + delay;
longTimeoutId = setTimeout(function() {
longTimeoutId = null;
callback();
}, delay);
}
document.addEventListener("deviceready", function() {
document.addEventListener("resume", function() {
if (longTimeoutId) {
setLongTimeout(callback, longTimeoutDate - Date.now();
}
});
});

SignalR Stops Working After A While

For some reason, SignalR will just stop calling client methods after a short period of time (about 1 hour or less I estimate). I have a page that shows Alerts... a very simple implementation. Here's the Javascript:
$(function () {
// enable logging for debugging
$.connection.hub.logging = true;
// Declare a proxy to reference the hub.
var hub = $.connection.alertHub;
hub.client.addAlert = function (id, title, url, dateTime) {
console.log(title);
};
$.connection.hub.start().done(function () {
console.log("Alert Ready");
});
});
If I refresh the page, it works again for about an hour, then will stop calling the client event addAlert. There are no errors in the log, no warnings. The last event in log (other than the pings to the server) is:
[15:18:58 GMT-0600 (CST)] SignalR: Triggering client hub event
'addAlert' on hub 'AlertHub'.
Many of these events will come in for a short while, then just stop, even though the server should still be sending them.
I am using Firefox 35.0.1 on Mac and SignalR 2.0.0.
I realize that a work-around is to force a page refresh every 10 mins or so, but I'm looking for a way to fix the root cause of the problem.
I enabled SignalR tracing on the server. I created an "alert" on the server after a fresh refresh of the Alert page and the alert came through. I waited about 10 mins and I tried it again, and it failed to come through. Here's what the logs read (sorry for the verbosity, not sure what was relevant):
SignalR.Transports.TransportHeartBeat Information: 0 : Connection b8b21c4c-22b4-4686-9098-cb72c904d4c9 is New.
SignalR.Transports.TransportHeartBeat Verbose: 0 : KeepAlive(b8b21c4c-22b4-4686-9098-cb72c904d4c9)
SignalR.Transports.TransportHeartBeat Verbose: 0 : KeepAlive(b8b21c4c-22b4-4686-9098-cb72c904d4c9)
SignalR.Transports.TransportHeartBeat Verbose: 0 : KeepAlive(b8b21c4c-22b4-4686-9098-cb72c904d4c9)
SignalR.Transports.TransportHeartBeat Verbose: 0 : KeepAlive(b8b21c4c-22b4-4686-9098-cb72c904d4c9)
There are dozens more of the SignalR.Transports.TransportHeartBeat messages, but nothing else.
i think theres a timeout of default 110 seconds for signalr. Can you try signalr disconnected event to reconnect it back.
$.connection.hub.disconnected(function () {
setTimeout(function () {
startHub();
}, 5000);
});
and in startHub() you can start connection again.
reference : https://github.com/SignalR/SignalR/issues/3128
and How to use SignalR events to keep connection alive in the right way?
As it turns out the problem was the way I was handling the AlertHub connections. I am using Enterprise Library Caching to store connections backing the AlertHub, and I was expiring the cache entries 20 minutes after they were created. Ergo, when the server called the client method, no errors where reported because there were no client(s) to send the message(s) to.
I have since increased the cache expiration to a reasonable value, which solved the problem.
You can refresh page if client is inactive, no mouse movement (in about every 15-30 min). I had same problem and solved it that way. That was nasty workaround but later i forgot about it and never fixed it completly ;)

Javascript event for mobile browser re-launch or device wake

Morning all, I'm looking for some kind of Javascript event I can use to detect when a mobile browser window regains focus, after either a user closes/minimizes their browser (to go back to a home screen/different app), or if the device resumes from sleep (either the user powering it off, or it going to sleep after a screen timeout).
I'd like to be able to find a single event that works for everything, but I know that's unlikely! The pageshow event works for iOS devices, but it's rather sketchy for use with everything else. I've tried focus and DOMActivate but neither of them seem to have the desired effect.
The page may not always have form elements on it, and I don't really want the user to have to touch the page again to trigger the event.
The requirement for such an event is caused by our code periodically checking for new content by making XHR requests. These are never sent when the browser is asleep, so we never get new content to restart the timeouts.
Thanks for any help you guys may be able to provide!
We had a similar issue and solved it something like this:
var lastSync = 0;
var syncInterval = 60000; //sync every minute
function syncPage() {
lastSync = new Date().getTime(); //set last sync to be now
updatePage(); //do your stuff
}
setInterval(function() {
var now = new Date().getTime();
if ((now - lastSync) > syncInterval ) {
syncPage();
}
}, 5000); //check every 5 seconds whether a minute has passed since last sync
This way you would sync every minute if your page is active, and if you put your browser in idle mode for over a minute, at most 5 seconds will pass before you sync upon opening the browser again. Short intervals might drain the battery more than you would like, so keep that in mind when adapting the timings to you needs.
Better than an interval would be to add a window blur listener and a window focus listener. On blur, record current time. On focus, validate you are still logged in / sync'd / whatever you need to do.
Basically exactly the same thing but it runs only when necessary rather than slowing your entire page down with an interval.
Update
var $window = $(window),
$window.__INACTIVITY_THRESHOLD = 60000;
$window.add(document.body); //necessary for mobile browsers
$window.declareActivity = function () { $window.__lastEvent = new Date(); };
$window.blur($window.declareActivity);
$window.focus(function(){
var diff = (new Date()) - $window.__lastEvent;
if (diff > $window.__INACTIVITY_THRESHOLD) {
$window.trigger("inactivity");
}
});
$window.on("inactivity", "", null, function () {
//your inactivity code
});
Though that blur event seems sketchy if the phone is powering off and I don't know that I would trust it in all circumstances / mobile devices. So I'd probably throw in something like this:
$(document.body).on("click scroll keyup", "", null, $window.declareActivity);
so that my inactivity timer works for when the user just walks away as well. Depending on your site, you may want to adjust that exact event list - or simply throw in a $window.declareActivity(); into your existing scripts that respond to user inputs.

Cancel Javascript timeout

I have a long process hosted on a Web Server. The thing is triggered from a Web Page, at the click of a button by a user. Some Javascript polls regularly via Ajax to check if the operation has completed server side. To do this, I use setInterval, and later on clearInterval to stop polling.
If this takes too long (e.g. server has crashed), I'd like the client to be informed by some sort of timeout. I've done some research and found about setTimeout. Problem is, if the operation finishes successfully before the timeout, I'd like to cancel this one.
How to do this ?
Would you suggest a different approach ?
PS : I'm targetting IE7/IE8 in particular, but always open to some JQuery
As long as you store your interval's id in a variable, you can use it to clear the interval at any time.
var interval = window.setInterval(yourFunction, 10000);
...elsewhere...
window.clearTimeout(interval);
For more information see the Mozilla Documentation's setInterval example.
Put together a quick JS Fiddle containing a modified version of Mozilla's Example.
To clear a setTimeout, use clearTimeout.
You want two timers (as you said)
repeating interval to do the next poll and
one-time expiration to give up if the server never responds
If the polling is successful you want to clear both the polling interval and cancel the failure timer. If the expiration timer fires you want to clear the polling interval
var checkCount = 0;
function checkComplete() {
console.log("test " + checkCount);
if (checkCount++ > 10) {
console.log("clearing timeout");
window.clearInterval(pollInterval);
window.clearTimeout(expireTimer);
}
}
function cancelPolling(timer) {
console.log("clearing poll interval");
window.clearInterval(pollInterval);
}
var pollInterval = window.setInterval(checkComplete, 500);
var expireTimer = window.setTimeout(cancelPolling, 10000);
You can fiddle with the checkCount constant "10" - keep it low to simulate polling success, raise it higher for the timeout to occur before the checkCount is reached, simulating polling failure.

Categories

Resources