I was testing my website out with chrome throttling, using the low-end mobile. I noticed one of my JS intervals were running much slower than it should be for no reason. I decided to investigate further, so I wrote this code:
let prev = Date.now();
let x = 0;
setInterval(() => {
if(x++ % 100 == 0) {
console.log(Date.now() - prev);
prev = Date.now();
}
}, 10);
It sets an interval to run every 10ms, and it prints the time elapsed every 100 runs. This should be 1 second, but it is printing 4-8 seconds. This shouldn't happen, because getting the date every 100 runs, and then logging it, should be a piece of cake, even for "low-end mobile".
My best guess right now is that chrome's throttling also specifically throttles the event loop, which sometimes gives misleading results, like here. Is this true?
Related
I'm trying to make a clock for a Libet task - a cognitive task used by psychologists. By convention these clocks take 2560 ms to complete a revolution. Mine seems to be running quite a lot slower and I can figure out why.
I have made an example of the issue here: https://jsfiddle.net/Sumoza/re4v7Lcj/8/
On each iteration of a setInterval with a 1ms delay, I increase the angle of rotation of the hand by (Math.PI*2)/2560, i.e. 1/2560th of a circle in radians. So, incrementing one of these each ms should complete the circle in that number of ms. As you can see from the clock, this runs a fair bit slower. Can anyone shed any light as to why. Interestingly, *10 seems to look a lot closer to what I want - but that doesn't make much sense to me so I'm wary of the fix. Relevant JS from the fiddle below. Thanks for any help.
var time = 0;
var rad_tick = (Math.PI*2)/2560; //radians per tick: last number is ms per revolution
// Draw Clock
const clock = document.getElementById('clock')
const clock_ctx = clock.getContext('2d')
clock_ctx.translate(clock.width/2, clock.height/2);
radius = (clock.height/2) * 0.7;
function drawClock() {
clock_ctx.clearRect(-clock.width/2, -clock.height/2, clock.width, clock.height);
clock.style.backgroundColor = 'transparent';
clock_ctx.beginPath();
clock_ctx.arc(0, 0, radius, 0 , 2 * Math.PI);
clock_ctx.stroke();
}
drawClock();
// Draw Hand
const hand = document.getElementById('hand')
const hand_ctx = hand.getContext('2d')
hand_ctx.translate(hand.width/2, hand.height/2);
function drawHand(time){
hand_ctx.clearRect(-hand.width/2, -hand.height/2, hand.width, hand.height);
hand_ctx.beginPath();
hand_ctx.moveTo(0,0);
hand_ctx.rotate(time);
hand_ctx.lineTo(0, -radius*0.9);
hand_ctx.stroke();
hand_ctx.rotate(-time);
}
function drawTime(){
time+=rad_tick;//*10
drawHand(time)
}
var intervalId = setInterval(drawTime, 1);
Try this
var starttime = new Date().getTime();
function drawTime() {
var elapsed = new Date().getTime() - starttime;
var time = elapsed % 2560;
time = time * rad_tick;
console.log(elapsed + "->" + time);
drawHand(time)
}
Base things on the current time, and don't depend on setInterval to fire at the exact time you set it to fire. If there are things in-queue at the time that setInterval is supposed to fire, it will do those things in-queue first and only then run the function you've wired to setIterval (causing the delays you see --which build up more and more over time). Learn about the event loop and requestAnimationFrame.
Instead, get the time (freshly) prior to doing something, and calculate how much time has passed since the start time. Base your animations on these calculations. This way, even if there are slight delays, your last action will always sync things to the way they're supposed to look right now.
When you do it this way, you might lose some frames (skipping things the engine didn't have time to do), but it will likely complete much closer to the expected completion time.
The bottom line is, if you tell the browser to do more than it can do in a particular amount of time, there will be delays. However, you can skip frames of an animation to make things complete much closer to the expected completion time.
You ask setInterval to call your callback every 1ms, but does it actually call it every 1ms?
Try this code:
let cnt = 0;
setInterval(() => cnt++, 1);
setTimeout(() => { console.log(cnt); }, 1000);
For me, it prints something between 230 and 235. So looks like the actual (average) minimum interval for me is a bit over 4ms.
Adding on to Stig, since javascript isn't super fast when it comes to these things, it won't be super accurate. Using something like getTime() will allow proper timing. How about you call getTime in your drawTime function, so that around every millisecond, it will check the actual time and use that value.
I build a countdown timer, so - When I stay in the countdown chrome tab, its work fine.
But when I go to another tab (while the countdown) running, its made it slower.
for example when you start countdown for 30 seconds, and you go to another tab for 10 seconds, when you go back to the countdown tab it will be in 24 seconds - instead of 20 seconds (its just example its very changeable).
stopper = setTimeout(progressCountdown, 1000);
the progressCountdown function its not needed here because its work fine when I in the countdown tab.
There is some option to fix that?
A timeout is not guaranteed to be run after exactly the given delay. In fact, there can be a number of reasons why the delay will be longer than the one you give, for instance if your web page or CPU is overloaded with work, or if it's throttled by the browser to save power and battery (which is what many browsers do with background tabs). MDN has a list with many reasons why these things may occur.
What seems to be your actual problem, however, is that you're using the timeouts to track time itself. This will often be very inaccurate due to the above problems. What you should do instead is to use a clock to keep track of the time spent before the timeout fires:
// performance.now() is a good way of measuring durations
let atStart = performance.now();
function timeoutCallback() {
let now = performance.now();
// milliseconds is accurate now matter how long the timeout actually took
// no matter if in foreground or in background tab
let milliseconds = now - atStart;
// number of seconds left
let secondsLeft = Math.ceil(30 - milliseconds / 1000);
console.log('Countdown: ' + secondsLeft);
if(secondsLeft > 0)
setTimeout(timeoutCallback, 1000);
}
setTimeout(timeoutCallback, 1000);
I build a web app and I use setInterval with 500ms timer for some clock.
When the window is active the clock runs perfect, I use that:
var tempTimer = 0;
setInterval(function () {
goTimer()
}, 500);
function goTimer() {
tempTimer++;
$("#timer").val(tempTimer);
}
But the problem is when the window/tab becomes inactive - the interval is changed to 1000ms!
When i focus the window/tab again, it changes back to 500ms.
check this out: http://jsfiddle.net/4Jw37/
Thanks a bunch.
Yes, this behavior is intentional.
See the MDN article:
In (Firefox 5.0 / Thunderbird 5.0 / SeaMonkey 2.2) and Chrome 11,
timeouts are clamped to firing no more often than once per second
(1000ms) in inactive tabs; see bug 633421 for more information about
this in Mozilla or crbug.com/66078 for details about this in Chrome.
And the spec says:
Note: This API does not guarantee that timers will run exactly on
schedule. Delays due to CPU load, other tasks, etc, are to be
expected.
You seem to want a timer that increments every half second. You can do that much more accurately by keeping track of the total time since you started and doing some math.
var tempTimer = 0;
var startedTimer = Date.now();
setInterval(goTimer, 250); // a little more often in case of drift
function goTimer() {
tempTimer = Math.floor((Date.now() - startedTimer) / 500);
$("#timer").val(tempTimer);
}
See this work here: http://jsfiddle.net/4Jw37/2/
So this does update every half second, but it doesn't need to. If it skips a few beats it will fix itself the next time it fires because it's recalculating each time based on the time since it started tracking. The number of times the function runs is now has no effect on the value of the counter.
So put in another way, do not ask how many times you incremented the count. Instead ask how many half second segments have passed since you started.
For time interval, Browsers may not behave similar for both active and inactive window.
What you can do, When you are setting the time interval you can save the timestamp(initial time). On window.onfocus, take on there timestamp (now) find the difference between initial time and now and use that update the tempTimer.
This question already has answers here:
How do browsers pause/change Javascript when tab or window is not active?
(3 answers)
Closed 9 years ago.
Here's the simplest code for reproducing I could think of:
ms = 30; // 1000 ?
num = 1;
function test()
{
num+=ms;
document.getElementById('Submit').value = num; // Using native Javascript on purpose
if (num < 4000)
window.setTimeout(test, ms);
}
test()
I set the ms (milliseconds between iterations) to 30, ran the script and moved to different tab on the browser.
Then I wait for about 10 seconds (the script should finish within 4 seconds) and came back to the tab.
If I used Firefox I saw that the script has not finished, and the numbers are still running (resuming from where I left them, I guess).
Which is annoying enough,
But if I changed ms to 1000 and repeat the above steps, when I come back to the tab I saw the script has indeed already finished.
(The script should still take 4 seconds to finish).
Namely, sometimes Firefox runs window.setTimeout even if the window is out of focus, and sometimes it doesn't. Possibly depending on the duration
On the other hand, this is not happening with Internet Explorer.
It keeps running the script even if the tab is not focused. No matter how I set the ms.
Is that due to some performance considerations of Firefox?
What exactly is happening?
How come such a basic thing is in consistent between browsers,
nowadays?
OR, am I working wrong? Is it a weird way for coding?
I'm just trying repeatedly change the DOM, in a delayed fashion, without using setInterval (because I'm changing the interval it self on the go).
And most important, how should I regard this?
I can't assume my user won't leave the tab.
I want to allow my user to leave the page for as mush as one might like.
If one leaves and come back after half an hour, he/she will see irrelevant animations on the page, still running.
Some of these animations are seen by all the users connecting to the page.
There is no need they will be synchronized in resolution of milliseconds, but I can't start them only when the user put the tab/window in focus.
I'm using Firefox 25.0.1, and IE 11. (Windows 7)
Most modern browsers (especially on mobile devices) suspend execution of scripts in tabs that are out of focus to save CPU cycles (for instance, this is why requestAnimationFrame was brought to life). In the case of timeouts, shorter intervals are actually changed to a different / higher value as the browser vendor sees fit.
What you can do to overcome this (if you really must know the interval between successive executions) is to set a timestamp when the timeout is activated, and compare it with the timestamp when the timeout handler is actually executed. Note that when you're animating it's best to calculate properties of the animated Objects by taking other application variables into account, rather than rely on the amount of calls a particular handler has had.
You could also attach listeners to the window for "(un)focus" Events to know when the user has "come back" to your application. In this event handler you can verify whether a timeout was pending and execute its callback manually, if you must do so.
see the difference: http://jsfiddle.net/qN6eB/
ms = 30; // 1000 ?
num = 1;
start = new Date();
function test()
{
num+=ms;
document.getElementById('Submit').value = num;
if (num < 4000)
window.setTimeout(test, ms);
else
document.getElementById('Time').value = new Date() - start;
}
test()
ms2 = 30; // 1000 ?
num2 = 1;
start2 = new Date();
dueTo = new Date(+new Date()+4000);
function test2()
{
num2+=ms2;
document.getElementById('Submit2').value = num2;
if (new Date() < dueTo)
window.setTimeout(test2, ms2);
else
document.getElementById('Time2').value = new Date() - start2;
}
test2()
setTimeout is not precise for timing. Because the timer doesn't interrupt the process, I'll wait for idle time. I don't know how the browsers are managing it, but an inactive tab probably has a lower priority.
I can think about two solutions :
- Try setInterval (I'm not sure if this will solve your problem or not)
- Instead of incrementing a variable, use a Date object, containing the time at the beginning, and compare it with the current time when the function is executed.
var beginTime = (new Date()).getTime();
var intervalId = setInterval(function() {
var timePassed = (new Date()).getTime() - beginTime;
document.getElementById('Submit').value = timePassed;
if(timePassed >= 4000) {
clearInterval(intervalId);
}
}, 30);
I'm trying to make a countdown that is counting down in milliseconds; however, the countdown actually takes much longer than 7 seconds. Any idea as to why?
function countDown(time){
var i = 0;
var interval = setInterval(function(){
i++;
if(i > time){
clearInterval(interval);
}else{
//mining
$('#mining_time').text($('#mining_time').text()-1);
}
}, 1);
}
And I can confirm the varible time passed to the function is correctly set to 7000.
For a mostly-accurate countdown, use setTimeout().
setTimeout(fn, 7e3);
If you absolutely must have it as close to 7 seconds as possible, use a tight poll (requestAnimationFrame()) and look at difference between the time of start and current poll.
var startTime = Date.now();
requestAnimationFrame(function me() {
var deltaTime = Date.now() - startTime;
if (deltaTime >= 7e3) {
fn();
} else {
requestAnimationFrame(me);
}
});
Poly-fill as required.
the most precise way to run something after 7 seconds - is to use setTimeout with 7000 ms interval
a. there is no browser that guarantees an interval to run with 1ms resolution. In the best case it would be 7-10ms
b. there is only one thread in js, so the tasks are queued. It means that the next run will be scheduled to only after the current run is finished.
Some useful reading: http://ejohn.org/blog/how-javascript-timers-work/
No browser will take 1 as parameter for setInterval. Off the top of my head the minimum is 4 ms.
For an accurate result, get the current time, add 7000 ms, and poll (using setInterval or setTimeout) until you reach that new time.
A quick Web search returned this article that provides an example.
[Update] the value of 4 ms is mentioned on this MDN page.