Last active
March 30, 2024 17:08
-
-
Save FaberVitale/7767ef773116c4a7e537322bbb8476e6 to your computer and use it in GitHub Desktop.
poll.ts
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
/** | |
* Returns a promise that resolves after `waitMs` milliseconds. | |
* @param waitMs the amount of milliseconds we need to wait. | |
*/ | |
export const delay = (waitMs: number): Promise<void> => new Promise(resolve => setTimeout(resolve, waitMs)); | |
export interface PollOptions { | |
waitMs: number; | |
onError?: (error: unknown, ctx: PollContext) => void; | |
} | |
export interface PollContext { | |
signal: AbortSignal; | |
stopPolling: () => void; | |
} | |
type PollTask = ((ctx: PollContext) => void) | ((ctx: PollContext) => Promise<void>); | |
const defaultOnError: NonNullable<PollOptions['onError']> = (err) => { | |
console.error(err); | |
}; | |
/** | |
* Invokes input task multiple times with a delay of `options.waitMs` between. | |
* Returns an unsubscribe function to stop polling. | |
* | |
* ### Task context | |
* Tasks are called with {@link PollContext | context} as argument. | |
* A task can stop the polling with `ctx.stopPolling()`. | |
* The context object also contains a signal object | |
* to notify asynchronously when polling has ended. | |
* | |
* ### `options.waitMs` | |
* A finite number greater or equal 0. | |
* If `options.waitMs` is 0 the task is not executed. | |
* | |
* @param {PollTask} task | |
* @param {PollOptions} options | |
* @throws {TypeError} If task is not a function. | |
* @throws {TypeError} If `options.waitMs` is not a finite number >= 0. | |
* @returns An unsubscribe function to stop polling. | |
*/ | |
export const poll = (task: PollTask, options: PollOptions): (() => void) => { | |
const { waitMs, onError: inputOnError } = options; | |
const controller = new AbortController(); | |
const onError = inputOnError ?? defaultOnError; | |
const stopPolling = (): void => { | |
controller.abort(); | |
}; | |
const ctx: PollContext = { | |
signal: controller.signal, | |
stopPolling, | |
}; | |
if (typeof task !== 'function') { | |
throw new TypeError('[poll] expected task to be a function'); | |
} | |
if (!Number.isFinite(waitMs) || waitMs < 0) { | |
throw new TypeError(`[poll] expected task to be a finite number greater or equal 0. Got ${waitMs} instead.`); | |
} | |
if (!waitMs) { | |
stopPolling(); | |
} | |
const runTask = async () => { | |
while (!controller.signal.aborted) { | |
try { | |
await task(ctx); | |
} catch (err) { | |
onError(err, ctx); | |
} | |
await delay(waitMs); | |
} | |
}; | |
runTask().catch(err => { | |
onError(err, ctx); | |
}); | |
return stopPolling; | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment