Skip to content

Instantly share code, notes, and snippets.

@fuweichin
Created December 2, 2022 11:39
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save fuweichin/d7e5d091e5c182529a70dd86f4f46e94 to your computer and use it in GitHub Desktop.
Save fuweichin/d7e5d091e5c182529a70dd86f4f46e94 to your computer and use it in GitHub Desktop.
setInterval implementations
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>setInterval implementations</title>
</head>
<body>
<div>
<p>native <code>setInterval</code> (w/ or w/o stabilization)</p>
<span id="time1">00:00:00.000</span>
</div>
<div>
<p>custom <code>setInterval</code> stabilized</p>
<span id="time2">00:00:00.000</span>
</div>
<div>
<p>custom <code>setInterval</code> with cumulative delay</p>
<span id="time3">00:00:00.000</span>
</div>
<script type="module">
import stabilized from './setInterval-stabilized.js';
import cumulativeDelayed from './setInterval-with-cumulative-delay.js';
function main1() {
let time = document.querySelector('#time1');
let count = 0;
let startTime = performance.now();
let handle = setInterval(() => {
let date = new Date();
count++;
time.textContent = formatDateToString(date) + ' ' + ((performance.now() - startTime) / count).toFixed(2);
}, 1000 / 5);
}
function main2() {
let {setInterval} = stabilized;
let time = document.querySelector('#time2');
let count = 0;
let startTime = performance.now();
let handle = setInterval(() => {
let date = new Date();
count++;
time.textContent = formatDateToString(date) + ' ' + ((performance.now() - startTime) / count).toFixed(2);
}, 1000 / 5);
}
function main3() {
let {setInterval} = cumulativeDelayed;
let time = document.querySelector('#time3');
let count = 0;
let startTime = performance.now();
let handle = setInterval(() => {
let date = new Date();
count++;
time.textContent = formatDateToString(date) + ' ' + ((performance.now() - startTime) / count).toFixed(2);
}, 1000 / 5);
}
function detectSetIntervalMinimalInterval(count = 10) {
return new Promise((resolve) => {
let i = 0;
let startTime = performance.now();
let handle = setInterval(() => {
i++;
if (i === count) {
clearInterval(handle);
resolve((performance.now() - startTime) / count);
}
}, 1);
});
}
document.addEventListener('DOMContentLoaded', main1);
document.addEventListener('DOMContentLoaded', main2);
document.addEventListener('DOMContentLoaded', main3);
function formatDateToString(d) {
return d.getHours().toString().padStart(2, '0') + ':' + d.getMinutes().toString().padStart(2, '0') + ':' + d.getSeconds().toString().padStart(2, '0') + '.' + d.getMilliseconds().toString().padStart(3, '0');
}
</script>
</body>
</html>
/*!
* stabilized setInterval to overcome cumulative delay
* It simulates Chrome setInterval's tick strategy, can be used as a setInterval alternative for Safari / Firefox
*/
let nextIntervalHandle = 1;
let intervalHandleMap = {};
export function setInterval(callback, interval = 4) {
let fn;
let args;
if (typeof callback === 'function') {
fn = callback;
args = Array.prototype.slice.call(arguments, 2);
} else if (typeof callback === 'string') {
fn = eval;
args = [callback];
} else {
throw new TypeError('Invalid argument callback');
}
let delay;
if (typeof interval === 'number' && isFinite(interval) && interval >= 0) {
delay = interval < 4 ? 4 : interval;
} else {
throw new TypeError('Invalid argument delay');
}
let handle = nextIntervalHandle++;
let startTime = performance.now();
let previousTime = startTime;
let tick = () => {
let nowTime = performance.now();
let previousTimeoutDelay = nowTime - previousTime;
let deltaT1 = previousTimeoutDelay - delay;
let durationSinceStart = nowTime - startTime;
let numIntervals = Math.floor(durationSinceStart / delay);
let deltaT2 = durationSinceStart % (numIntervals * delay);
let deltaT = (deltaT1 + deltaT2) / 2;
let nextTimeoutDelay = deltaT < delay ? delay - deltaT : delay - (deltaT % delay);
previousTime = nowTime;
try {
fn.apply(this, args);
} finally {
let intervalInfo = intervalHandleMap[handle];
if (intervalInfo) {
intervalInfo.timeoutHandle = setTimeout(tick, nextTimeoutDelay);
}
}
};
intervalHandleMap[handle] = {
timeoutHandle: setTimeout(tick, delay),
};
return handle;
}
export function clearInterval(handle) {
if (handle !== handle >>> 0) {
return;
}
let intervalInfo = intervalHandleMap[handle];
if (!intervalInfo) {
return;
}
clearTimeout(intervalInfo.timeoutHandle);
delete intervalHandleMap[handle];
}
export default {
setInterval,
clearInterval,
};
/*!
* setInterval implementation with cumulative delay
* It simulates Safari / Firefox setInterval's tick strategy, can be used as a setInterval alternative for Chrome
*/
let nextIntervalHandle = 1;
let intervalHandleMap = {};
export function setInterval(callback, interval = 4) {
let fn;
let args;
if (typeof callback === 'function') {
fn = callback;
args = Array.prototype.slice.call(arguments, 2);
} else if (typeof callback === 'string') {
fn = eval;
args = [callback];
} else {
throw new TypeError('Invalid value for argument callback');
}
let delay;
if (typeof interval === 'number' && isFinite(interval) && interval >= 0) {
delay = interval < 4 ? 4 : interval;
} else {
throw new TypeError('Invalid value for argument delay');
}
let handle = nextIntervalHandle++;
let tick = () => {
try {
fn.apply(this, args);
} finally {
let intervalInfo = intervalHandleMap[handle];
if (intervalInfo) {
intervalInfo.timeoutHandle = setTimeout(tick, delay + 10);
}
}
};
intervalHandleMap[handle] = {
timeoutHandle: setTimeout(tick, delay + 10),
};
return handle;
}
export function clearInterval(handle) {
if (handle !== handle >>> 0) {
return;
}
let intervalInfo = intervalHandleMap[handle];
if (!intervalInfo) {
return;
}
clearTimeout(intervalInfo.timeoutHandle);
delete intervalHandleMap[handle];
}
export default {
setInterval,
clearInterval,
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment