version 1.1.0

This commit is contained in:
Josh Guyette 2023-07-17 19:45:31 -05:00
parent 617fdebee3
commit 1f46191482
4 changed files with 186 additions and 161 deletions

View File

@ -1,6 +1,6 @@
# Schedule Later # Schedule Later
schedule-later is a static class that provides methods for managing date-time based tasks, such as starting timeouts and intervals at a specific time of day. Under-the-hood, it uses `setTimeout` and `setInterval`. schedule-later provides methods for managing date-time based tasks, such as starting timeouts and intervals at a specific time of day. Under-the-hood, it uses `setTimeout` and `setInterval`.
## Install ## Install
@ -17,7 +17,7 @@ yarn add schedule-later
## Import ## Import
```typescript ```typescript
import { Scheduler, TimeInMS } from 'schedule-later' import { startTimeout, startInterval, TimeInMS } from 'schedule-later'
``` ```
## Key Concepts ## Key Concepts
@ -55,10 +55,7 @@ The `Scheduler` class provides two main static methods: `startTimeout` and `star
The `startTimeout` method starts a timeout that calls a given function after a specific delay. The delay is calculated based on the `TimeUntil` object passed to it. The method returns a `StopFunction` (see below). The `startTimeout` method starts a timeout that calls a given function after a specific delay. The delay is calculated based on the `TimeUntil` object passed to it. The method returns a `StopFunction` (see below).
```typescript ```typescript
public static startTimeout( function startTimeout(timerFunc: Function, start: TimeUntil): StopFunction
timerFunc: Function,
start: TimeUntil
): StopFunction;
``` ```
### startInterval ### startInterval
@ -66,11 +63,11 @@ public static startTimeout(
The `startInterval` method starts an interval that calls a given function repeatedly with a fixed time delay between each call. Like `startTimeout`, the initial delay is calculated based on a `TimeUntil` object. The method returns a `StopFunction` (see below). The `startInterval` method starts an interval that calls a given function repeatedly with a fixed time delay between each call. Like `startTimeout`, the initial delay is calculated based on a `TimeUntil` object. The method returns a `StopFunction` (see below).
```typescript ```typescript
public static startInterval( function startInterval(
intervalFunc: Function, intervalFunc: Function,
intervalMS: number, intervalMS: number,
start?: TimeUntil start?: TimeUntil
): StopFunction; ): StopFunction
``` ```
## Stop Functions ## Stop Functions

View File

@ -1,6 +1,6 @@
{ {
"name": "schedule-later", "name": "schedule-later",
"version": "1.0.6", "version": "1.1.0",
"types": "dist/Scheduler.d.ts", "types": "dist/Scheduler.d.ts",
"main": "dist/Scheduler.js", "main": "dist/Scheduler.js",
"author": "Nightness", "author": "Nightness",
@ -18,6 +18,6 @@
"@types/jest": "^29.5.3", "@types/jest": "^29.5.3",
"@types/node": "^18.11.18", "@types/node": "^18.11.18",
"jest": "^29.6.1", "jest": "^29.6.1",
"typescript": "^4.6.4" "typescript": "^5.1.6"
} }
} }

View File

@ -1,6 +1,6 @@
import { Scheduler } from './Scheduler' import { startTimeout, startInterval, TimeInMS } from './Scheduler'
describe('Scheduler', () => { describe('setTimeout Tests', () => {
beforeEach(() => { beforeEach(() => {
jest.useFakeTimers() jest.useFakeTimers()
}) })
@ -9,10 +9,10 @@ describe('Scheduler', () => {
jest.clearAllTimers() jest.clearAllTimers()
}) })
test('startTimeout should correctly start and stop timeout', () => { test('startTimeout, should start and stop properly', () => {
const callback = jest.fn() const callback = jest.fn()
const stop = Scheduler.startTimeout(callback, { ms: 5000 }) const stop = startTimeout(callback, { ms: 5000 })
// Advance timers by less than the delay and check if callback has not been called // Advance timers by less than the delay and check if callback has not been called
jest.advanceTimersByTime(2000) jest.advanceTimersByTime(2000)
@ -26,10 +26,30 @@ describe('Scheduler', () => {
stop() stop()
}) })
test('startInterval should correctly start and stop interval', () => { test('startTimeout, should not call the function if stop function is called', () => {
const fn = jest.fn()
const stop = startTimeout(fn, { ms: TimeInMS.SECOND })
expect(fn).not.toBeCalled()
stop()
jest.advanceTimersByTime(TimeInMS.SECOND)
expect(fn).not.toBeCalled()
})
})
describe('setInterval Tests', () => {
beforeEach(() => {
jest.useFakeTimers()
})
afterEach(() => {
jest.clearAllTimers()
})
test('startInterval, should correctly start and repeat interval', () => {
const callback = jest.fn() const callback = jest.fn()
const stop = Scheduler.startInterval(callback, 2000, { ms: 5000 }) const stop = startInterval(callback, 2000, { ms: 5000 })
// Advance timers by less than the initial delay and check if callback has not been called // Advance timers by less than the initial delay and check if callback has not been called
jest.advanceTimersByTime(2000) jest.advanceTimersByTime(2000)
@ -46,4 +66,14 @@ describe('Scheduler', () => {
// Cleanup // Cleanup
stop() stop()
}) })
test('startInterval, should not call the function if stop function is called', () => {
const fn = jest.fn()
const stop = startInterval(fn, 2000, { ms: TimeInMS.SECOND })
expect(fn).not.toBeCalled()
stop()
jest.advanceTimersByTime(TimeInMS.SECOND)
expect(fn).not.toBeCalled()
})
}) })

View File

@ -26,156 +26,154 @@ export type StopCancelFunction = (stopRunning: boolean) => void
export type StopFunction = (stopTime?: TimeUntil) => StopCancelFunction | null export type StopFunction = (stopTime?: TimeUntil) => StopCancelFunction | null
export class Scheduler { function timeUntil(start: TimeUntil) {
private static timeUntil(start: TimeUntil) { if (start.ms) {
if (start.ms) { return start.ms
return start.ms
}
// get the current date
let now = new Date()
// set the target time
const targetTime = start.timeOfDay
? new Date(
now.getFullYear(),
now.getMonth(),
now.getDate(),
start.timeOfDay.hour,
start.timeOfDay.minute ?? 0,
start.timeOfDay.seconds ?? 0,
0
)
: start.date ?? now
// if the target time has already passed today, set it for tomorrow
if (start.timeOfDay && now > targetTime) {
targetTime.setDate(targetTime.getDate() + 1)
}
// calculate the delay until the next target time
// @ts-ignore - TS doesn't know this is allowed
let delay = targetTime - now
return delay
} }
/** // get the current date
* Start a timeout let now = new Date()
* @param {Function} timerFunc
* @param {TimeUntil} start
* @return {StopFunction}
*/
public static startTimeout(
timerFunc: Function,
start: TimeUntil
): StopFunction {
let delay = this.timeUntil(start)
// set a timeout to start the interval at the target time // set the target time
let timeout: NodeJS.Timeout | null = null const targetTime = start.timeOfDay
timeout = setTimeout(function () { ? new Date(
// Clear the timeout variable now.getFullYear(),
timeout = null now.getMonth(),
// call the function immediately now.getDate(),
timerFunc() start.timeOfDay.hour,
}, delay) start.timeOfDay.minute ?? 0,
start.timeOfDay.seconds ?? 0,
0
)
: start.date ?? now
const stopNow = () => { // if the target time has already passed today, set it for tomorrow
if (timeout) clearTimeout(timeout) if (start.timeOfDay && now > targetTime) {
} targetTime.setDate(targetTime.getDate() + 1)
// stop() function to stop the interval and timeout
// stop(stopInMS) will stop the interval and timeout in stopInMS milliseconds
// stop(stopHour, stopMinute) will stop the interval and timeout at the next stopHour:stopMinute
const stop = (stopTime?: TimeUntil): StopCancelFunction | null => {
if (stopTime === undefined) {
stopNow()
return null
}
if ((stopTime as any) instanceof Date) {
// @ts-ignore - TS doesn't know this is allowed
const timeFromNow = stopTime - new Date()
const stopTimeout = setTimeout(stopNow, timeFromNow)
// stopRunning is a boolean that will either cancel the stop (false), or stop the interval now (true)
return (stopRunning: boolean = false) => {
clearTimeout(stopTimeout)
if (stopRunning) stopNow()
}
}
const stopTimeout = setTimeout(stopNow, this.timeUntil(stopTime))
// stopRunning is a boolean that will either cancel the stop (false), or stop the interval now (true)
return (stopRunning: boolean = false) => {
clearTimeout(stopTimeout)
if (stopRunning) stopNow()
}
}
// return a cleanup function
return stop
} }
/** // calculate the delay until the next target time
* Start an interval // @ts-ignore - TS doesn't know this is allowed
* @param {Function} intervalFunc let delay = targetTime - now
* @param {number} intervalMS
* @param {TimeUntil} start
* @return {StopFunction}
*/
public static startInterval(
intervalFunc: Function,
intervalMS: number,
start?: TimeUntil
): StopFunction {
let delay = start ? this.timeUntil(start) : 0
// set a timeout to start the interval at the target time return delay
let interval: number | null = null }
let timeout: NodeJS.Timeout | null = setTimeout(function () {
// Clear the timeout variable /**
timeout = null * Start a timeout
// start the interval * @param {Function} timerFunc
interval = setInterval(intervalFunc, intervalMS) * @param {TimeUntil} start
// call the function immediately * @return {StopFunction}
intervalFunc() */
}, delay) export function startTimeout(
timerFunc: Function,
const stopNow = () => { start: TimeUntil
if (timeout) clearTimeout(timeout) ): StopFunction {
if (interval) clearInterval(interval) let delay = timeUntil(start)
}
// set a timeout to start the interval at the target time
// stop() function to stop the interval and timeout let timeout: NodeJS.Timeout | null = null
// stop(stopInMS) will stop the interval and timeout in stopInMS milliseconds timeout = setTimeout(function () {
// stop(stopHour, stopMinute) will stop the interval and timeout at the next stopHour:stopMinute // Clear the timeout variable
const stop = (stopTime?: TimeUntil): StopCancelFunction | null => { timeout = null
if (stopTime === undefined) { // call the function immediately
stopNow() timerFunc()
return null }, delay)
}
const stopNow = () => {
if ((stopTime as any) instanceof Date) { if (timeout) clearTimeout(timeout)
// @ts-ignore - TS doesn't know this is allowed }
const timeFromNow = stopTime - new Date()
const stopTimeout = setTimeout(stopNow, timeFromNow) // stop() function to stop the interval and timeout
// stopRunning is a boolean that will either cancel the stop (false), or stop the interval now (true) // stop(stopInMS) will stop the interval and timeout in stopInMS milliseconds
return (stopRunning: boolean = false) => { // stop(stopHour, stopMinute) will stop the interval and timeout at the next stopHour:stopMinute
clearTimeout(stopTimeout) const stop = (stopTime?: TimeUntil): StopCancelFunction | null => {
if (stopRunning) stopNow() if (stopTime === undefined) {
} stopNow()
} return null
}
const stopTimeout = setTimeout(stopNow, this.timeUntil(stopTime))
// stopRunning is a boolean that will either cancel the stop (false), or stop the interval now (true) if ((stopTime as any) instanceof Date) {
return (stopRunning: boolean = false) => { // @ts-ignore - TS doesn't know this is allowed
clearTimeout(stopTimeout) const timeFromNow = stopTime - new Date()
if (stopRunning) stopNow() const stopTimeout = setTimeout(stopNow, timeFromNow)
} // stopRunning is a boolean that will either cancel the stop (false), or stop the interval now (true)
} return (stopRunning: boolean = false) => {
clearTimeout(stopTimeout)
// return a cleanup function if (stopRunning) stopNow()
return stop }
} }
const stopTimeout = setTimeout(stopNow, timeUntil(stopTime))
// stopRunning is a boolean that will either cancel the stop (false), or stop the interval now (true)
return (stopRunning: boolean = false) => {
clearTimeout(stopTimeout)
if (stopRunning) stopNow()
}
}
// return a cleanup function
return stop
}
/**
* Start an interval
* @param {Function} intervalFunc
* @param {number} intervalMS
* @param {TimeUntil} start
* @return {StopFunction}
*/
export function startInterval(
intervalFunc: Function,
intervalMS: number,
start?: TimeUntil
): StopFunction {
let delay = start ? timeUntil(start) : 0
// set a timeout to start the interval at the target time
let interval: number | null = null
let timeout: NodeJS.Timeout | null = setTimeout(function () {
// Clear the timeout variable
timeout = null
// start the interval
interval = setInterval(intervalFunc, intervalMS)
// call the function immediately
intervalFunc()
}, delay)
const stopNow = () => {
if (timeout) clearTimeout(timeout)
if (interval) clearInterval(interval)
}
// stop() function to stop the interval and timeout
// stop(stopInMS) will stop the interval and timeout in stopInMS milliseconds
// stop(stopHour, stopMinute) will stop the interval and timeout at the next stopHour:stopMinute
const stop = (stopTime?: TimeUntil): StopCancelFunction | null => {
if (stopTime === undefined) {
stopNow()
return null
}
if ((stopTime as any) instanceof Date) {
// @ts-ignore - TS doesn't know this is allowed
const timeFromNow = stopTime - new Date()
const stopTimeout = setTimeout(stopNow, timeFromNow)
// stopRunning is a boolean that will either cancel the stop (false), or stop the interval now (true)
return (stopRunning: boolean = false) => {
clearTimeout(stopTimeout)
if (stopRunning) stopNow()
}
}
const stopTimeout = setTimeout(stopNow, timeUntil(stopTime))
// stopRunning is a boolean that will either cancel the stop (false), or stop the interval now (true)
return (stopRunning: boolean = false) => {
clearTimeout(stopTimeout)
if (stopRunning) stopNow()
}
}
// return a cleanup function
return stop
} }