chore: internalise waitUntil
parent
f360ad6045
commit
8346abff41
|
|
@ -0,0 +1,281 @@
|
||||||
|
// Source: https://github.com/devlato/async-wait-until/blob/master/src/index.ts
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This module implements a function that waits for a given predicate to be truthy.
|
||||||
|
* Relies on Promises and supports async/await.
|
||||||
|
* @packageDocumentation
|
||||||
|
* @module async-wait-until
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @type Error JavaScript's generic Error type
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Timeout error, which is thrown when timeout passes but the predicate
|
||||||
|
* doesn't resolve with a truthy value
|
||||||
|
* @public
|
||||||
|
* @class
|
||||||
|
* @exception
|
||||||
|
* @category Exceptions
|
||||||
|
*/
|
||||||
|
export class TimeoutError extends Error {
|
||||||
|
/**
|
||||||
|
* Creates a TimeoutError instance
|
||||||
|
* @public
|
||||||
|
* @param timeoutInMs Expected timeout, in milliseconds
|
||||||
|
*/
|
||||||
|
constructor(timeoutInMs?: number) {
|
||||||
|
super(timeoutInMs != null ? `Timed out after waiting for ${timeoutInMs} ms` : 'Timed out');
|
||||||
|
|
||||||
|
Object.setPrototypeOf(this, TimeoutError.prototype);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A utility function for cross-platform type-safe scheduling
|
||||||
|
* @private
|
||||||
|
* @returns Returns a proper scheduler instance depending on the current environment
|
||||||
|
* @throws Error
|
||||||
|
* @category Utilities
|
||||||
|
*/
|
||||||
|
const getScheduler = (): Scheduler => ({
|
||||||
|
schedule: (fn, interval) => {
|
||||||
|
let scheduledTimer: number | NodeJS.Timeout | undefined = undefined;
|
||||||
|
|
||||||
|
const cleanUp = (timer: number | NodeJS.Timeout | undefined) => {
|
||||||
|
if (timer != null) {
|
||||||
|
clearTimeout(timer as number);
|
||||||
|
}
|
||||||
|
|
||||||
|
scheduledTimer = undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
const iteration = () => {
|
||||||
|
cleanUp(scheduledTimer);
|
||||||
|
fn();
|
||||||
|
};
|
||||||
|
|
||||||
|
scheduledTimer = setTimeout(iteration, interval);
|
||||||
|
|
||||||
|
return {
|
||||||
|
cancel: () => cleanUp(scheduledTimer),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delays the execution by the given interval, in milliseconds
|
||||||
|
* @private
|
||||||
|
* @param scheduler A scheduler instance
|
||||||
|
* @param interval An interval to wait for before resolving the Promise, in milliseconds
|
||||||
|
* @returns A Promise that gets resolved once the given interval passes
|
||||||
|
* @throws Error
|
||||||
|
* @category Utilities
|
||||||
|
*/
|
||||||
|
const delay = (scheduler: Scheduler, interval: number): Promise<void> =>
|
||||||
|
new Promise((resolve, reject) => {
|
||||||
|
try {
|
||||||
|
scheduler.schedule(resolve, interval);
|
||||||
|
} catch (e) {
|
||||||
|
reject(e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Platform-specific scheduler
|
||||||
|
* @private
|
||||||
|
* @category Defaults
|
||||||
|
*/
|
||||||
|
const SCHEDULER: Scheduler = getScheduler();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default interval between attempts, in milliseconds
|
||||||
|
* @public
|
||||||
|
* @category Defaults
|
||||||
|
*/
|
||||||
|
export const DEFAULT_INTERVAL_BETWEEN_ATTEMPTS_IN_MS = 50;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default timeout, in milliseconds
|
||||||
|
* @public
|
||||||
|
* @category Defaults
|
||||||
|
*/
|
||||||
|
export const DEFAULT_TIMEOUT_IN_MS = 5000;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Timeout that represents infinite wait time
|
||||||
|
* @public
|
||||||
|
* @category Defaults
|
||||||
|
*/
|
||||||
|
export const WAIT_FOREVER = Number.POSITIVE_INFINITY;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Waits for predicate to be truthy and resolves a Promise
|
||||||
|
* @public
|
||||||
|
* @param predicate A predicate function that checks the condition, it should return either a truthy value or a falsy value
|
||||||
|
* @param options Options object (or *(deprecated)*: a maximum wait interval, *5000 ms* by default)
|
||||||
|
* @param intervalBetweenAttempts *(deprecated)* Interval to wait for between attempts, optional, *50 ms* by default
|
||||||
|
* @returns A promise to return the given predicate's result, once it resolves with a truthy value
|
||||||
|
* @template T Result type for the truthy value returned by the predicate
|
||||||
|
* @throws [[TimeoutError]] An exception thrown when the specified timeout interval passes but the predicate doesn't return a truthy value
|
||||||
|
* @throws Error
|
||||||
|
* @see [[TruthyValue]]
|
||||||
|
* @see [[FalsyValue]]
|
||||||
|
* @see [[Options]]
|
||||||
|
*/
|
||||||
|
export const waitUntil = <T extends PredicateReturnValue>(
|
||||||
|
predicate: Predicate<T>,
|
||||||
|
options?: number | Options,
|
||||||
|
intervalBetweenAttempts?: number,
|
||||||
|
): Promise<T> => {
|
||||||
|
const timerTimeout = (typeof options === 'number' ? options : options?.timeout) ?? DEFAULT_TIMEOUT_IN_MS;
|
||||||
|
const timerIntervalBetweenAttempts =
|
||||||
|
(typeof options === 'number' ? intervalBetweenAttempts : options?.intervalBetweenAttempts) ??
|
||||||
|
DEFAULT_INTERVAL_BETWEEN_ATTEMPTS_IN_MS;
|
||||||
|
|
||||||
|
const runPredicate = (): Promise<ReturnType<Predicate<T>>> =>
|
||||||
|
new Promise((resolve, reject) => {
|
||||||
|
try {
|
||||||
|
resolve(predicate());
|
||||||
|
} catch (e) {
|
||||||
|
reject(e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let isTimedOut = false;
|
||||||
|
|
||||||
|
const predicatePromise = (): Promise<T> =>
|
||||||
|
new Promise<T>((resolve, reject) => {
|
||||||
|
const iteration = () => {
|
||||||
|
if (isTimedOut) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
runPredicate()
|
||||||
|
.then((result) => {
|
||||||
|
if (result) {
|
||||||
|
resolve(result);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
delay(SCHEDULER, timerIntervalBetweenAttempts).then(iteration).catch(reject);
|
||||||
|
})
|
||||||
|
.catch(reject);
|
||||||
|
};
|
||||||
|
|
||||||
|
iteration();
|
||||||
|
});
|
||||||
|
|
||||||
|
const timeoutPromise =
|
||||||
|
timerTimeout !== WAIT_FOREVER
|
||||||
|
? () =>
|
||||||
|
delay(SCHEDULER, timerTimeout).then(() => {
|
||||||
|
isTimedOut = true;
|
||||||
|
throw new TimeoutError(timerTimeout);
|
||||||
|
})
|
||||||
|
: undefined;
|
||||||
|
|
||||||
|
return timeoutPromise != null ? Promise.race([predicatePromise(), timeoutPromise()]) : predicatePromise();
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The predicate type
|
||||||
|
* @private
|
||||||
|
* @template T Returned value type, either a truthy value or a falsy value
|
||||||
|
* @throws Error
|
||||||
|
* @category Common Types
|
||||||
|
* @see [[TruthyValue]]
|
||||||
|
* @see [[FalsyValue]]
|
||||||
|
*/
|
||||||
|
export type Predicate<T extends PredicateReturnValue> = () => T | Promise<T>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A type that represents a falsy value
|
||||||
|
* @private
|
||||||
|
* @category Common Types
|
||||||
|
*/
|
||||||
|
export type FalsyValue = null | undefined | false | '' | 0 | void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A type that represents a truthy value
|
||||||
|
* @private
|
||||||
|
* @category Common Types
|
||||||
|
*/
|
||||||
|
export type TruthyValue =
|
||||||
|
| Record<string, unknown>
|
||||||
|
| unknown[]
|
||||||
|
| symbol
|
||||||
|
// eslint-disable-next-line no-unused-vars
|
||||||
|
| ((...args: unknown[]) => unknown)
|
||||||
|
| Exclude<number, 0>
|
||||||
|
| Exclude<string, ''>
|
||||||
|
| true;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A type that represents a Predicate's return value
|
||||||
|
* @private
|
||||||
|
* @category Common Types
|
||||||
|
*/
|
||||||
|
export type PredicateReturnValue = TruthyValue | FalsyValue;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Options that allow to specify timeout or time interval between consecutive attempts
|
||||||
|
* @public
|
||||||
|
* @category Common Types
|
||||||
|
*/
|
||||||
|
export type Options = {
|
||||||
|
/**
|
||||||
|
* @property Maximum wait interval, *5000 ms* by default
|
||||||
|
*/
|
||||||
|
timeout?: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @property Interval to wait for between attempts, optional, *50 ms* by default
|
||||||
|
*/
|
||||||
|
intervalBetweenAttempts?: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A function that schedules a given callback to run in given number of milliseconds
|
||||||
|
* @private
|
||||||
|
* @param callback A callback to execute
|
||||||
|
* @param interval A time interval to wait before executing the callback
|
||||||
|
* @returns An instance of ScheduleCanceler that allows to cancel the scheduled callback's execution
|
||||||
|
* @template T The callback params' type
|
||||||
|
* @throws Error
|
||||||
|
* @category Common Types
|
||||||
|
*/
|
||||||
|
type ScheduleFn = <T>(callback: (...args: T[]) => void, interval: number) => ScheduleCanceler; // eslint-disable-line no-unused-vars
|
||||||
|
/**
|
||||||
|
* A function that cancels the previously scheduled callback's execution
|
||||||
|
* @private
|
||||||
|
* @throws Error
|
||||||
|
* @category Common Types
|
||||||
|
*/
|
||||||
|
type CancelScheduledFn = () => void;
|
||||||
|
/**
|
||||||
|
* A stateful abstraction over Node.js & web browser timers that cancels the scheduled task
|
||||||
|
* @private
|
||||||
|
* @category Common Types
|
||||||
|
*/
|
||||||
|
type ScheduleCanceler = {
|
||||||
|
/**
|
||||||
|
* @property A function that cancels the previously scheduled callback's execution
|
||||||
|
*/
|
||||||
|
cancel: CancelScheduledFn;
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* A stateful abstraction over Node.js & web browser timers that schedules a task
|
||||||
|
* @private
|
||||||
|
* @category Common Types
|
||||||
|
*/
|
||||||
|
type Scheduler = {
|
||||||
|
/**
|
||||||
|
* @property A function that schedules a given callback to run in given number of milliseconds
|
||||||
|
*/
|
||||||
|
schedule: ScheduleFn;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default waitUntil;
|
||||||
Loading…
Reference in New Issue