timeout-flow
Version:
Fluent, composable, pauseable JavaScript timers and time control flows — plus RAF utilities for frame-based logic.
77 lines (62 loc) • 1.98 kB
JavaScript
// ./src/waitFor.js
import { parseDuration } from './parseDuration.js';
import { attachAbort, createAbortError } from './abort.js';
import { now } from './now.js'
/**
* Waits for a condition to become true, polling at intervals.
*
* @param {Function} condition - returns truthy when satisfied
* @param {Object} [options] - optional polling control
* @param {string|number} [options.interval='250ms'] - poll frequency
* @param {string|number} [options.timeout] - max wait time
* @param {boolean} [options.immediate=false] - If true, evaluate condition immediately
* @param {AbortSignal} [options.signal] - Optional AbortSignal to cancel waiting
* @returns {Promise<void>}
*/
export function waitFor(condition, { interval = '250ms', timeout, immediate = false, signal } = {}) {
return new Promise((resolve, reject) => {
if (signal?.aborted) {
reject(createAbortError());
return;
}
const start = now();
const intervalMs = typeof interval === 'string' ? parseDuration(interval) : interval;
const timeoutMs = timeout != null ? parseDuration(timeout) : null;
let poller = null;
const cleanup = () => {
if (poller != null) {
clearInterval(poller);
poller = null;
}
cleanupAbort();
};
const onAbort = () => {
cleanup();
reject(createAbortError());
};
const cleanupAbort = attachAbort(signal, onAbort);
const check = () => {
let ok = false;
try {
ok = !!condition();
} catch (err) {
cleanup();
reject(err);
return;
}
if (ok) {
cleanup();
resolve();
return;
}
if (timeoutMs != null && now() - start >= timeoutMs) {
cleanup();
reject(new Error('waitFor timed out'));
}
};
// Only evaluate immediately if explicitly requested
if (immediate) check();
// Schedule exactly once
poller = setInterval(check, intervalMs);
});
}