Created
January 4, 2025 20:54
-
-
Save tabarra/92cd341350039078c065b3e0e5174ca1 to your computer and use it in GitHub Desktop.
MinuteScheduler Class: Similar to setInterval, but aligned to the minute or a multiple of it, while also accounting for drift.
This file contains hidden or 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
const roundToNextDivisible = (num: number, interval: number) => { | |
return Math.ceil(num / interval) * interval; | |
}; | |
/** | |
* Similar to setInterval, but aligned to the minute or a multiple of it, while also accounting for drift. | |
*/ | |
export default class MinuteScheduler { | |
public tsLast = Date.now(); | |
constructor( | |
private readonly callback: () => void, | |
private readonly intervalMins = 1, | |
alignInitial = true, | |
) { | |
if (this.intervalMins < 1) { | |
throw new Error('intervalMins must be at least 1'); | |
} | |
if (this.intervalMins > 59) { | |
throw new Error('intervalMins must be at most 59'); | |
} | |
if (this.intervalMins % 1 !== 0) { | |
throw new Error('intervalMins must be an integer'); | |
} | |
if (intervalMins !== 1 && alignInitial) { | |
this.alignInitial(); | |
} else { | |
this.scheduleNext(); | |
} | |
} | |
/** | |
* Awaits until the minutes are divisible by this.intervalMins before starting the loop. | |
*/ | |
private async alignInitial() { | |
//If it's already aligned, start the loop | |
const now = new Date(); | |
if (now.getMinutes() % this.intervalMins === 0) { | |
this.run(); | |
return; | |
} | |
//Find the next minute divisible and wait for it | |
const nextRun = new Date(); | |
const currMinute = nextRun.getMinutes(); | |
const nextMinute = roundToNextDivisible(currMinute, this.intervalMins); | |
const nextRunTs = nextRun.setMinutes(nextMinute, 0, 0); | |
const msDelay = nextRunTs - Date.now(); | |
//Schedule first run | |
setTimeout(() => this.run(), msDelay); | |
} | |
/** | |
* Schedules the next run. | |
*/ | |
private scheduleNext() { | |
const now = new Date(); | |
const nextTs = now.setMinutes(now.getMinutes() + this.intervalMins, 0, 0); | |
const delay = nextTs - Date.now(); | |
setTimeout(() => this.run(), delay); | |
} | |
/** | |
* Runs the callback and schedules the next run. | |
*/ | |
private run() { | |
this.tsLast = Date.now(); | |
this.callback(); | |
this.scheduleNext(); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment