wait-utils
Version:
A modern, zero-dependency wait / timing utility toolkit for JavaScript and TypeScript.
247 lines (238 loc) • 8.33 kB
text/typescript
/**
* Error thrown when an operation is aborted via an `AbortSignal`.
*
* This error is used in asynchronous operations
* to indicate that the caller explicitly cancelled
* the request by invoking `AbortController.abort()`.
*/
declare class AbortError extends DOMException {
constructor(message?: string);
}
/**
* Error thrown when an operation exceeds its allowed time limit.
*
* This error is used in asynchronous operations to indicate
* that the operation took too long to complete and was
* terminated based on a timeout setting.
*/
declare class TimeoutError extends DOMException {
constructor(message?: string);
}
/**
* A hook invoked after each successful callback execution in {@link poll}.
*
* Can be used for logging, adjusting the next delay, or stopping the wait loop.
*
* This is skipped if the callback stops the wait or throws.
*
* @param context - The current {@link PollContext}.
*/
type AfterPollCallback<T = unknown> = (context: PollContext<T>) => unknown | Promise<unknown>;
/**
* The main function invoked at each iteration in {@link poll}.
*
* This function performs the primary asynchronous operation.
* To stop further attempts, set `context.stop = true`.
*
* @param context - The current {@link PollContext}.
*
* @returns A result value, or a Promise that resolves to one.
*/
type PollCallback<T = unknown, R = unknown> = (context: PollContext<T>) => R | Promise<R>;
/**
* Context object in {@link poll}.
*/
interface PollContext<T = unknown> {
/**
* The current attempt number, starting from `1` and incremented automatically.
* @readonly
*/
readonly attempt: number;
/**
* The delay (in milliseconds) before the next attempt.
*
* Can be updated dynamically to implement backoff, jitter, etc.
*/
delay?: number | null;
/**
* Set to `true` to stop further attempts.
*/
stop?: boolean;
/**
* User-provided data.
*
* Useful for sharing state or configuration across attempts.
*/
userData?: T;
}
/**
* Configuration options for {@link poll}.
*/
interface PollOptions<T = unknown> {
/**
* A function to run after each poll attempt.
*
* Can be used to log results, inspect attempt state, or modify future behavior.
*/
afterPoll?: AfterPollCallback<T>;
/**
* The delay (in milliseconds) between subsequent attempts.
*
* Can be changed dynamically via {@link PollContext.delay | context.delay}.
*/
delay?: number | null;
/**
* The delay (in milliseconds) before the first attempt.
* If not specified, falls back to {@link delay}.
*/
initialDelay?: number | null;
/**
* An {@link AbortSignal} to cancel the wait loop.
*
* If triggered, the function throws an `AbortError`.
*/
signal?: AbortSignal;
/**
* The maximum total duration (in milliseconds) to wait before timing out.
*
* If exceeded, the function throws a `TimeoutError`.
*/
timeout?: number;
/**
* User-provided data.
*
* Useful for sharing state or configuration across attempts.
*/
userData?: T;
}
/**
* Repeatedly invokes a callback function until it succeeds, is stopped, aborted, or times out.
*
* After each successful callback execution, an optional {@link PollOptions.afterPoll}
* hook is invoked. You can control retry timing by updating `context.delay` or exit
* early by setting `context.stop = true`.
*
* @typeParam T - The shape of the user data passed through the context.
* @typeParam R - The return type of the callback function.
*
* @param callback - The function to invoke on each attempt.
* @param options - Optional configuration to control timing, retries, and cancellation.
*
* @returns The last value returned by the callback.
*
* @throws `AbortError` if the operation is cancelled using `signal`.
* @throws `TimeoutError` if the total wait duration exceeds `timeout`.
*/
declare function poll<T, R>(callback: PollCallback<T, R>, options?: PollOptions<T>): Promise<R>;
/**
* Context object provided to the {@link setIntervalAsync} callback.
*/
interface IntervalContext {
/**
* The delay in milliseconds before the next tick.
* Defaults to the initial delay provided to {@link setIntervalAsync}.
* The callback can modify this property to change intervals dynamically.
*/
delay?: number;
/**
* When set to `true`, the inverval is stopped.
*/
stop?: boolean;
/**
* The tick counter, starting at `1` and incremented automatically.
* @readonly
* @remarks This property cannot be modified by the callback.
*/
readonly tickCount: number;
}
/**
* Asynchronously calls a callback repeatedly at a given interval.
*
* Internally uses `setTimeout`, so interval drift may occur.
*
* @param callback - Invoked on each tick. Receives a mutable {@link IntervalContext} object,
* allowing the callback to change delay dynamically or stop the interval.
* @param delay - The delay in milliseconds between invocations.
* Can be changed dynamically via `context.delay`.
* @param signal - An `AbortSignal` which can cancel the interval.
*
* @returns A promise that:
* - resolves when `context.stop` is set to `true` inside the callback (graceful termination),
* - rejects with `signal.reason` if the signal aborts,
* - rejects if the callback throws or rejects.
*
* @example
* ```ts
* let count = 0;
* setIntervalAsync(ctx => {
* console.log("tick", ++count);
* ctx.stop = count >= 5;
* }, 1000).then(() => {
* console.log("Completed after 5 ticks");
* });
* ```
*/
declare function setIntervalAsync(callback: (context: IntervalContext) => unknown | Promise<unknown>, delay?: number, signal?: AbortSignal): Promise<void>;
/**
* Asynchronously delays execution for the specified duration.
*
* @param delay - The number of milliseconds to wait.
* @param signal - An `AbortSignal` that can cancel the wait early.
*
* @returns A promise that resolves after the delay, or rejects with
* the `signal.reason` if `signal` is aborted before timeout.
*
* @example
* ```ts
* // Basic usage
* await setTimeoutAsync(1000);
*
* // Abortable usage
* const controller = new AbortController();
* setTimeoutAsync(2000, controller.signal)
* .catch(reason => console.log('Aborted due to', reason));
* controller.abort('Timeout canceled');
* ```
*/
declare function setTimeoutAsync(delay?: number, signal?: AbortSignal): Promise<void>;
/**
* Rejects with a `TimeoutError` after the specified delay,
* unless cancelled by an `AbortSignal`.
*
* @param delay - The number of milliseconds to wait before timing out.
* @param signal - Optional `AbortSignal` to cancel the timeout.
*
* @returns A promise that:
* - resolves if the signal is aborted before the delay
* - rejects with a `TimeoutError` if the delay completes
* - rethrows an error if an unexpected rejection occurs
*/
declare function timeout(delay?: number | null, signal?: AbortSignal): Promise<void>;
/**
* Waits for the specified number of milliseconds.
*
* Supports cancellation via an `AbortSignal`.
*
* @param delay - The number of milliseconds to wait.
* @param signal - Optional `AbortSignal` to cancel the wait early.
*
* @returns A promise that:
* - resolves after the delay
* - rejects with the `AbortSignal.reason` if cancelled before the delay
*/
declare function wait(delay?: number | null, signal?: AbortSignal): Promise<void>;
/**
* Waits until the specified time is reached.
*
* @param timestamp - Target time:
* - If a {@link Date}, relative to {@link Date.now}.
* - If a `number`, relative to {@link performance.now}.
*
* @param signal - Optional `AbortSignal` to cancel the wait early.
*
* @returns A promise that:
* - resolves when the current time is at or past the target time
* - rejects with the signal’s reason if cancelled before the target
*/
declare function waitUntil(timestamp?: Date | number | null, signal?: AbortSignal): Promise<void>;
export { AbortError, type AfterPollCallback, type IntervalContext, type PollCallback, type PollContext, type PollOptions, TimeoutError, poll, setIntervalAsync, setTimeoutAsync, timeout, wait, waitUntil };