type BooleanLike = undefined | null | boolean type Condition = () => BooleanLike | Promise type Action = () => void interface Options { // Timeout interval in milliseconds (default: 100ms) timeout?: number // Max attempts (default: 30) retries?: number // Resolve promise if max attempts reached (default: false) alwaysResolve?: boolean // Whether or not to backoff from the initial timeout (+= timeout each attempt) (default: true) backoff?: boolean } export function when(condition: Condition, actionOrOptions?: Action | Options, maybeOptions?: Options): Promise { const callback: Action | undefined = typeof actionOrOptions === 'function' ? actionOrOptions : undefined const options: Options = (typeof actionOrOptions === 'object' ? actionOrOptions : maybeOptions) || {} const { timeout = 100, retries = 30, alwaysResolve = false, backoff = true } = options let nextTimeout = timeout return new Promise((resolve, _reject) => { let attempts = 0 const done = () => { if (typeof callback === 'function') callback() resolve() } const check = async () => { if (attempts >= retries) { if (alwaysResolve) done() return } attempts++ if (backoff && attempts > 1) { nextTimeout += timeout } try { const result = await condition() if (result) { done() } else { setTimeout(check, nextTimeout) } } catch (error) { setTimeout(check, nextTimeout) } } check() }) }