Created
December 2, 2022 11:39
-
-
Save fuweichin/d7e5d091e5c182529a70dd86f4f46e94 to your computer and use it in GitHub Desktop.
setInterval implementations
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!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> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/*! | |
* 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, | |
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/*! | |
* 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