As part of my react app, I have some setTimeout method to run timer and if that reached zero there is prompt will be pops up, which is working fine as long as the browser in in active state, suppose when we switch between one browser tab to another, I had a issue in delay of execution of this setTimeout intervals, it takes more time to reach zero than the configured one or the initial value that we setting.
Let's say timeoutInterval is 120 seconds as default value, then if we switch another tab this setInterval execution latency issue occured. any solution for this ?
Below is my setInterval method to execute timer.
const [inactiveTimer, setInactiveTimer] = useState(120);
useEffect(() => {
const inactiveInterval = setInterval(() => {
if (timeoutInterval > 0) {
setInactiveTimer(timeoutInterval - 1);
} else if (timeoutInterval === 0) {
setShowModal(true);
}
}, 1000);
return () => {
clearInterval(inactiveInterval);
};
}, []);
When a tab is not active, the function can be called at a maximum of one per second. One option to solve this is to use Web Workers, because they run in separate process, hence are not slowed down.
Another option to solve this issue you can a hack by playing an ~empty sound which will force the browser to keep the regular performance.
More about this inactivation issue - https://codereview.chromium.org/6577021
Empty sound hack - How to make JavaScript run at normal speed in Chrome even when tab is not active?
Related
I have an auction site in MERN stack with socket.io and i seem to have this unsolvable problem which i think is related to browsers and basic JS
Flow:
whenever a product is added by admin, socket broadcasts it with all
of details (including time )to all clients.
the clients can bid on them and stuff.
if a user refreshes the screen , it requests the socket for latest
product time and starts countdown from that time.
Everything is fine except some times react-countdown is lagging 0.5 second to 1 second behind whenever page is refreshed (please note that problem does not occur when opening same auction on new tab)
Note: i have also tried self made Countdown timer using setInterval but the problem does not go away
I am seeking assistance with this problem and am willing to compensate someone for their time and efforts to work with me directly to resolve it. Any help would be greatly appreciated.
Using setInterval and setTimeout means you are at the mercy of the browser. Browsers will often slow down the execution of these if some other process is happening and return to it once that's done, or if you switch away to another tab, they will purposefully reduce the tick rate. The level of accuracy you require is not easily achieved.
Firstly, I would suggest that getting the time it ends, then finding the difference between then and now, and counting down from this value with 1s increments will aggravate this problem. If each "tick" is off by even a small amount, this will accumulate into a larger error. This is probably what the library is doing by default. It may have also been what you were doing when you made your own timer, but I'd need to see it to confirm.
Instead, you need to store the time it ends passed from the socket (or keep it in scope like below) and on each "tick", work out the difference between then and now, every single time.
You could do this by using react-countdown in controlled mode and doing this logic in the parent.
I've made up a function here which would be the thing that receives the time from the socket -- or it's called from it. Its pseudo-code.
const timer = useRef(null)
const [timeLeft, setTimeLeft] = useState(null) // In seconds
const handleSocketReceived = (({endTime}) => {
timer.current = setInterval(() => {
const newTimeLeft = endTime - Date.now() // Pseudo code, depends on what end time is, but basically, work out the diff
setTimeLeft(newTimeLeft)
}, 100) // Smaller period means more regular correction
}, [])
// ...
useEffect(() => {
return () => clearInterval(timer.current)
}, [])
// ...
<Countdown date={timeLeft} controlled={true} />
I am trying to detect user inactivity for a certain amount of time and based on it need to perform certain actions.
I am using localStorage for this so that i set the idle time start across all tabs open.
below is the relevant part of my code
const detect = () => {
//console.log(`time is ${idleStartTime}`);
if(Date.now() - getIdleStartTime() > ONE_HOUR){
console.log('idle time more than one hour....');
console.log(`${getIdleStartTime()} ended`);
alert('idle time more than one hour....');
} else {
idleAnimationFrameId = requestAnimationFrame(function() {
detect();
});
}
};
const startTimer = () => {
idleAnimationFrameId = requestAnimationFrame(function() {
detect();
});
};
function setIdleStartTime() {
return new Promise((resolve, reject) => {
if (storageAvailable('localStorage')) {
// Yippee!
localStorage.setItem('idleStartTime', Date.now());
console.log('idle time has been set...');
resolve(true);
}
else {
// Too bad, no localStorage for us
console.log('no local storage support...');
reject('no local storage support.')
}
});
}
I mark the user as active if by listing to the following events. ['mousedown', 'mousemove', 'keydown', 'scroll', 'touchstart'];
function setEventListners() {
activityEvents.forEach(function(eventName) {
document.addEventListener(eventName, activity, true);
});
}
function activity() {
console.log('user activity detected...');
localStorage.setItem('idleStartTime', Date.now());
}
I see that in some cases there are 1.5K request sent in a very short time to localstorage to set the value. I see user activity detected... printed on the console around 1.5k times in matter of seconds.
My couple of questions are
will there be any performance issues as i am setting the value idleStartTime in localStorage and local storage set will be called thousands of time in a few seconds.
Are there any better alertnatives than using localStorage for my scenario.
Thanks.
Answer to Question-1
There will be no memory limit issue since every time you call localStorage.setItem, you are overwriting on the previous value. But you mentioned that your activity change is firing 1.5K times in a very short amount of time. That will increase disk usage(I/O) time.
Answer to Question-2
You can use setTimeout instead of event listeners. Many website use setTimeout 1 or 2 minutes to detect idle time. The logic is to check whether all your input fields are same as 2 minutes ago. If they are same then it is considered that the page is in idle mode for that amount of time. I know this approach seems ancient, but it will significantly reduce I/O time.
You can skip cursor move event. That alone will reduce the checking in a huge amount.
I have a jQuery Mobile web app which targets iOS and Android devices. A component of the application is a background task, which periodically checks for a.) changes to local data and b.) connectivity to the server. If both are true, the task pushes the changes.
I'm using a simple setTimeout()-based function to execute this task. Each failure or success condition calls setTimeout() on the background task, ensuring that it runs on 30 second intervals. I update a status div with the timestamp of the last task runtime for debugging purposes.
In any desktop browser, this works just fine; however, on iOS or Android, after some period of time, the task stops executing. I'm wondering if this is related to the power conservation settings of the devices--when iOS enters stand-by, does it terminate JavaScript execution? That is what appears to happen.
If so, what is the best way to resume? Is there an on-wake event which I can hook into? If not, what other options are there which don't involve hooking into events dependent on user interaction (I don't want to bind the entire page to a click event just to restart the background task).
Looks like Javascript execution is paused on MobileSafari when the browser page isn't focused. It also seems if setInterval() events are late, they are simply fired as soon as the browser is focused. This means we should be able to keep a setInterval() running, and assume the browser lost/regained focus if the setInterval function took much longer than usual.
This code alerts after switching back from a browser tab, after switching back from another app, and after resuming from sleep. If you set your threshold a bit longer than your setTimeout(), you can assume your timeout wouldn't finish if this fires.
If you wanted to stay on the safe side: you could save your timeout ID (returned by setTimeout) and set this to a shorter threshold than your timeout, then run clearTimeout() and setTimeout() again if this fires.
<script type="text/javascript">
var lastCheck = 0;
function sleepCheck() {
var now = new Date().getTime();
var diff = now - lastCheck;
if (diff > 3000) {
alert('took ' + diff + 'ms');
}
lastCheck = now;
}
window.onload = function() {
lastCheck = new Date().getTime();
setInterval(sleepCheck, 1000);
}
</script>
Edit: It appears this can sometimes trigger more than once in a row on resume, so you'd need to handle that somehow. (After letting my android browser sleep all night, it woke up to two alert()s. I bet Javascript got resumed at some arbitrary time before fully sleeping.)
I tested on Android 2.2 and the latest iOS - they both alert as soon as you resume from sleep.
When the user switches to another app or the screen sleeps, timers seem to pause until the user switches back to the app (or when the screen awakens).
Phonegap has a resume event you can listen to instead of polling for state (as well as a pause event if you want to do things before it is out of focus). You start listening to it after deviceReady fires.
document.addEventListener("deviceready", function () {
// do something when the app awakens
document.addEventListener('resume', function () {
// re-create a timer.
// ...
}, false);
}, false);
I use angular with phonegap and I have a service implemented that manages a certain timeout for me but basically you could create an object that sets the timer, cancels the timer and most importantly, updates the timer (update is what is called during the 'resume' event).
In angular I have a scopes and root scope that I can attach data to, my timeout is global so I attach it to root scope but for the purpose of this example, I'll simply attach it to the document object. I don't condone that because you need should apply it to some sort of scope or namespace.
var timeoutManager = function () {
return {
setTimer: function (expiresMsecs) {
document.timerData = {
timerId: setTimeout(function () {
timeoutCallback();
},
expiresMsecs),
totalDurationMsecs: expiresMsecs,
expirationDate: new Date(Date.now() += expiresMsecs)
};
},
updateTimer: function () {
if (document.timerData) {
//
// Calculate the msecs remaining so it can be used to set a new timer.
//
var timerMsecs = document.timerData.expirationDate - new Date();
//
// Kill the previous timer because a new one needs to be set or the callback
// needs to be fired.
//
this.cancelTimer();
if (timerMsecs > 0) {
this.setTimer(timerMsecs);
} else {
timeoutCallback();
}
}
},
cancelTimer: function () {
if (document.timerData && document.timerData.timerId) {
clearTimeout(document.timerData.timerId);
document.timerData = null;
}
}
};
};
You could have the manager function take a millisecond parameter instead of passing it into set, but again this is modeled somewhat after the angular service I wrote. The operations should be clear and concise enough to do something with them and add them to your own app.
var timeoutCallback = function () { console.log('timer fired!'); };
var manager = timeoutManager();
manager.setTimer(20000);
You will want to update the timer once you get the resume event in your event listener, like so:
// do something when the app awakens
document.addEventListener('resume', function () {
var manager = timeoutManager();
manager.updateTimer();
}, false);
The timeout manager also has cancelTimer() which can be used to kill the timer at any time.
You can use this class github.com/mustafah/background-timer based on #jlafay answer , where you can use as follow:
coffeescript
timer = new BackgroundTimer 10 * 1000, ->
# This callback will be called after 10 seconds
console.log 'finished'
timer.enableTicking 1000, (remaining) ->
# This callback will get called every second (1000 millisecond) till the timer ends
console.log remaining
timer.start()
javascript
timer = new BackgroundTimer(10 * 1000, function() {
// This callback will be called after 10 seconds
console.log("finished");
});
timer.enableTicking(1000, function(remaining) {
// This callback will get called every second (1000 millisecond) till the timer ends
console.log(remaining);
});
timer.start();
Hope it helps, Thank you ...
You should use the Page Visibility API (MDN) which is supported just about everywhere. It can detect if a page or tab has become visible again and you can then resume your timeouts or carry out some actions.
I have read from multiple places that setTimeout() is preferable to setInterval() when setting something up to basically run forever. The code below works fine but after about an hour of running Firefox (38.0.1) throws an error of too much recursion.
Essentially I have it grabbing a very small amount of text from counts.php and updating a table with that information. The whole call and return takes about 50ms according to the inspectors. I'm trying to have it do this every x seconds as directed by t.
I suspect if I switch to setInterval() this would probably work, but I wasn't sure what the current state of the setTimeout() vs setInterval() mindset is as everything I've been finding is about 3-5 years old.
$(document).ready(function() {
t = 3000;
$.ajaxSetup({cache: false});
function countsTimer(t) {
setTimeout(function () {
$.getJSON("counts.php", function (r) {
$(".count").each(function(i,v) {
if ($(this).html() != r[i]) {
$(this).fadeOut(function () {
$(this)
.css("color", ($(this).html() < r[i]) ? "green" : "red")
.html(r[i])
.fadeIn()
.animate({color: '#585858'}, 10000);
})
};
});
t = $(".selected").html().slice(0,-1) * ($(".selected").html().slice(-1) == "s" ? 1000 : 60000);
countsTimer(t);
});
}, t);
};
countsTimer(t);
});
Update: This issue was resolved by adding the .stop(true, true) before the .fadeOut() animation. This issue only occurred in Firefox as testing in other browsers didn't cause any issues. I have marked the answer as correct in spite of it not being the solution in this particular case but rather it offers a good explanation in a more general sense.
You should indeed switch to setInterval() in this case. The problem with setInterval() is that you either have to keep a reference if you ever want to clear the timeout and in case the operation (possibly) takes longer to perform than the timeout itself the operation could be running twice.
For example if you have a function running every 1s using setInterval, however the function itself takes 2s to complete due to a slow XHR request, that function will be running twice at the same time at some point. This is often undesirable. By using setTimout and calling that at the end of the original function the function never overlaps and the timeout you set is always the time between two function calls.
However, in your case you have a long-running application it seems, because your function runs every 3 seconds, the function call stack will increase by one every three seconds. This cannot be avoided unless you break this recursion loop. For example, you could only do the request when receiving a browser event like click on the document and checking for the time.
(function()
{
var lastCheck = Date.now(), alreadyRunning = false;
document.addEventListener
(
"click",
function()
{
if(!alreadyRunning && Date.now() - lastCheck > 3000)
{
alreadyRunning = true;
/* Do your request here! */
//Code below should run after your request has finished
lastCheck = Date.now();
alreadyRunning = false;
}
}
)
}());
This doesn't have the drawback setInterval does, because you always check if the code is already running, however the check only runs when receiving a browser event. (Which is normally not a problem.) And this method causes a lot more boilerplate.
So if you're sure the XHR request won't take longer than 3s to complete, just use setInterval().
Edit: Answer above is wrong in some aspects
As pointed out in the comments, setTimeout() does indeed not increase the call stack size, since it returns before the function in the timeout is called. Also the function in the question does not contain any specific recursion. I'll keep this answer because part of the question are about setTimeout() vs setInterval(). However, the problem causing the recursion error will probably be in some other piece of code since there is not function calling itself, directly or indirectly, anywhere in the sample code.
Is there a way to know when the browser is actively running requestAnimationFrame?
For example when I switch tabs from one that was running requestAnimationFrame, the function stops getting executed, when I switch back it continues, what is the best way to deal with this?
To detect if requestAnimationFrame is running 100% you can check:
window.addEventListener('blur', function() {
//not running full
}, false);
and
window.addEventListener('focus', function() {
//running optimal (if used)
}, false);
this can be used as we know requestAnimationFrame reduces trigger rate (in most browsers) when window (tab) is not the active one (IF being used - it depends on the code actually using the requestAnimationFrame).
If you want it to run constantly you can insert a mechanism such as this:
var isActiveTab = true; //update with the events above
function myLoop() {
//my cool stuff here
if (isActiveTab) {
requestAnimationFrame(myLoop);
} else {
setTimeout(myLoop, 16); //force a rate (vblank sync not necessary
//when display isn't updated
}
}
Note that the reduction in rate for requestAnimationFrame is not part of the standard and is a browser specific implementation.
When you again come back to the tab with animation,It must be working fine(If thats the case--following is your answer!!!)
This is what RAF made for.To optimize performance.
SetInterval and Settimeout can be used instead for creating animations, But they cannot interact with the browser and eventually end up hogging up the cpu and the performance is also quite slow.
But your question is really not a question.This is actually a trick used by RAF to better your overall animation experience.
There are several articles which explains RAF.
http://creativejs.com/resources/requestanimationframe/
Just An Optimization TRICK--No need to worry about it
A solution I used in a project for canvas repainting. It's not 100% accurate but it works for out of focus users
// This will run when user is inactive
let = handleVisibilityChange = () => {
if (document.hidden) {
setTimeout(() => {
updateYourStuff();
handleVisibilityChange();
}, 1000);
}
};
// Listen if user is active or inactive
document.addEventListener("visibilitychange", handleVisibilityChange, false);
// Your loop when user is active
function myLoop() {
updateYourStuff();
requestAnimationFrame(myLoop);
}
If you need to know at what time a frame was painted, you can call requestPostAnimationFrame (google canary) or use a polyfill for it.