Reputation: 122906
I was testing the accuracy of setTimeout
using this test. Now I noticed that (as expected) setTimeout
is not very accurate but for most appliances not dramatically inaccurate. Now if I run the test in Chrome and let it run in a background tab (so, switching to another tab and browse on there), returning to the test and inspecting the results (if the test finished) they are dramatically changed. It looks like the timeouts have been running a lot slower. Tested in FF4 or IE9 this didn't occur.
So it looks like Chrome suspends or at least slows down javascript execution in a tab that has no focus. Couldn't find much on the internet on the subject. It would mean that we can't run background tasks, like for example checking periodically on a server using XHR calls and setInterval
(I suspect to see the same behavior for setInterval
, will write a test if time is with me).
Has anyone encountered this? Would there be a workaround for this suspension/slowing down? Would you call it a bug and should I file it as such?
Upvotes: 200
Views: 172586
Reputation: 10801
In case you need run setTimeout(callback, 0)
(i.e. create an immediate macrotask) in background, you can use MessageChannel
. Chrome doesn't throttle it like setTimeout
.
const channel = new MessageChannel();
channel.port1.onmessage = callback;
channel.port2.postMessage(null);
Note: Chrome throttles setTimeout
not only in background tabs but in hidden iframes. This method works in both cases.
Upvotes: 1
Reputation: 1
Browsers are designed to optimize user experience and battery life, and they restrict the activity of background tabs to conserve CPU and power.
2 ways to dodge background execution supression is (example on ping/pong messages):
1st -
inline:
// Web Worker code defined as a string
const workerCode = `
// When the worker receives a message...
onmessage = function(e) {
// ...if that message is 'start'
if (e.data === 'start') {
// ...set an interval to send a heartbeat every minute
setInterval(() => {
// Fetch a heartbeat from the server
fetch('http://127.0.0.1:9000/heartbeat')
.then(response => {
// If the response isn't okay, throw an error
if (!response.ok) {
throw new Error('Network response was not ok');
}
return response.json();
})
.then(data => {
// Log the received heartbeat
console.log('Heartbeat received:', data);
})
.catch(error => {
// If there's an error, log it
console.error('Fetch error:', error.message);
});
}, 60000);
}
};
`;
// Create a new Blob object from the worker code
const blob = new Blob([workerCode], { type: 'application/javascript' });
// Create a new Web Worker from the blob URL
const worker = new Worker(URL.createObjectURL(blob));
// Post a 'start' message to the worker to begin the heartbeat
worker.postMessage('start');
2nd -
worker.js file:
// When the worker receives a message...
onmessage = function(e) {
// ...if that message is 'start'
if (e.data === 'start') {
// ...set an interval to send a heartbeat every minute
setInterval(() => {
// Fetch a heartbeat from the server
fetch('http://127.0.0.1:9000/heartbeat')
.then(response => {
// If the response isn't okay, throw an error
if (!response.ok) {
throw new Error('Network response was not ok');
}
return response.json();
})
.then(data => {
// Log the received heartbeat
console.log('Heartbeat received:', data);
})
.catch(error => {
// If there's an error, log it
console.error('Fetch error:', error.message);
});
}, 60000);
}
};
main section:
// Create a new Web Worker from the external worker file
const worker = new Worker('worker.js');
// Post a 'start' message to the worker to begin the heartbeat
worker.postMessage('start');
Upvotes: 0
Reputation: 3945
For unit tests you can run your chrome/chromium with argument: --disable-background-timer-throttling
Upvotes: 6
Reputation: 1645
Playing an empty sound forces the browser to retain the performance. I discovered it after reading this comment: How to make JavaScript run at normal speed in Chrome even when tab is not active?
With the source of that comment found here:
The Chromium insider also clarified that aggressive throttling will be automatically disabled for all background tabs “playing audio” as well as for any page where an “active websocket connection is present.”
I need unlimited performance on-demand for a browser game that uses WebSockets, so I know from experience that using WebSockets doesn't ensure unlimited performance, but from tests, playing an audio file seems to ensure it
Here's two empty audio loops I created for this purpose, you can use them freely, commercially: http://adventure.land/sounds/loops/empty_loop_for_js_performance.ogg http://adventure.land/sounds/loops/empty_loop_for_js_performance.wav
(They include -58db noise, -60db doesn't work)
I play them, on user-demand, with Howler.js: https://github.com/goldfire/howler.js
function performance_trick()
{
if(sounds.empty) return sounds.empty.play();
sounds.empty = new Howl({
src: ['/sounds/loops/empty_loop_for_js_performance.ogg','/sounds/loops/empty_loop_for_js_performance.wav'],
volume:0.5,
autoplay: true, loop: true,
});
}
It's sad that there is no built-in method to turn full JavaScript performance on/off by default, yet, crypto miners can hijack all your computing threads using Web Workers without any prompt.
Upvotes: 24
Reputation: 853
here is my solution which gets the current millisecond, and compares it to the millisecond that the function was created. for interval, it will update the millisecond when it runs the function. you can also grab the interval/timeout by an id.
<script>
var nowMillisTimeout = [];
var timeout = [];
var nowMillisInterval = [];
var interval = [];
function getCurrentMillis(){
var d = new Date();
var now = d.getHours()+""+d.getMinutes()+""+d.getSeconds()+""+d.getMilliseconds();
return now;
}
function setAccurateTimeout(callbackfunction, millis, id=0){
nowMillisTimeout[id] = getCurrentMillis();
timeout[id] = setInterval(function(){ var now = getCurrentMillis(); if(now >= (+nowMillisTimeout[id] + +millis)){callbackfunction.call(); clearInterval(timeout[id]);} }, 10);
}
function setAccurateInterval(callbackfunction, millis, id=0){
nowMillisInterval[id] = getCurrentMillis();
interval[id] = setInterval(function(){ var now = getCurrentMillis(); if(now >= (+nowMillisInterval[id] + +millis)){callbackfunction.call(); nowMillisInterval[id] = getCurrentMillis();} }, 10);
}
//usage
setAccurateTimeout(function(){ console.log('test timeout'); }, 1000, 1);
setAccurateInterval(function(){ console.log('test interval'); }, 1000, 1);
</script>
Upvotes: -2
Reputation: 154838
I recently asked about this and it is behaviour by design. When a tab is inactive, only at a maximum of once per second the function is called. Here is the code change.
Perhaps this will help: How can I make setInterval also work when a tab is inactive in Chrome?
TL;DR: use Web Workers.
Upvotes: 139
Reputation: 39
I have released worker-interval npm package which setInterval and clearInterval implementation with using Web-Workers to keep up and running on inactive tabs for Chrome, Firefox and IE.
Most of the modern browsers (Chrome, Firefox and IE), intervals (window timers) are clamped to fire no more often than once per second in inactive tabs.
You can find more information on
https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/setInterval
Upvotes: 4
Reputation: 1213
There is a solution to use Web Workers, because they run in separate process and are not slowed down
I've written a tiny script that can be used without changes to your code - it simply overrides functions setTimeout, clearTimeout, setInterval, clearInterval
Just include it before all your code
http://github.com/turuslan/HackTimer
Upvotes: 40
Reputation: 1564
I updated my jQuery core to 1.9.1, and it solved the Interval discrepancy in inactive tabs. I would try that first, then look into other code override options.
Upvotes: 0