Skip to content

Instantly share code, notes, and snippets.

@arvinall
Last active May 10, 2023 13:39
Show Gist options
  • Save arvinall/0423b907934599b00f33486671e5eeb1 to your computer and use it in GitHub Desktop.
Save arvinall/0423b907934599b00f33486671e5eeb1 to your computer and use it in GitHub Desktop.
Create multiple timers with just one setTimeout call
function createPromise () {
let resolve, reject
// eslint-disable-next-line promise/param-names
const promise = new Promise((...args) => ([resolve, reject] = args))
return { promise, resolve, reject }
}
const remove = (a, y) => (
index => (index >= 0)
? [...a.slice(0, index), ...a.slice(index + 1)]
: a
)(a.findIndex(x => (x === y)))
export const createTimer = () => (eventTarget => {
let { promise, resolve, reject } = createPromise()
let canceled = false
Promise.resolve().then(async () => {
while (!canceled) {
promise.catch(() => (canceled = true))
await new Promise(r => setTimeout(r, 1))
resolve()
;({ promise, resolve, reject } = createPromise())
}
})
const timers = {}
const cbs = {}
async function addCallback (cb, delay) {
eventTarget.addEventListener(delay, cb)
cbs[delay] = [...(cbs[delay] || []), cb]
if (timers[delay]) return
timers[delay] = performance.now()
while (!canceled && timers[delay]) {
try { await promise } catch (e) {}
const now = performance.now()
if ((now - timers[delay]) >= delay) {
timers[delay] = now
eventTarget.dispatchEvent(new Event(delay))
}
}
}
function removeCallback (cb, delay) {
if (!cbs[delay] || !cbs[delay].includes(cb)) return
eventTarget.removeEventListener(delay, cb)
cbs[delay] = remove(cbs[delay], cb)
if (!cbs[delay].length) {
delete cbs[delay]
delete timers[delay]
}
}
return {
set: addCallback, unset: removeCallback,
destroy () {
reject()
delete this.set
delete this.unset
delete this.destroy
}
}
})(new EventTarget())
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment