Skip to content

Instantly share code, notes, and snippets.

@FaberVitale
Last active March 30, 2024 17:08
Show Gist options
  • Save FaberVitale/7767ef773116c4a7e537322bbb8476e6 to your computer and use it in GitHub Desktop.
Save FaberVitale/7767ef773116c4a7e537322bbb8476e6 to your computer and use it in GitHub Desktop.
poll.ts
/**
* 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