Sometimes there's no event to wait on — you just have to keep asking until something becomes true. "Is the job done yet? …now? …now?" Polling calls a function on a fixed interval until a condition holds, with a timeout so it can't wait forever. It's how you wait for a background job, a file to appear, or an element to render when no callback is available.
Implement pollUntil(fn, predicate, options). Call fn, and if predicate(result) is truthy, resolve with that result. Otherwise wait interval ms and poll again — until the predicate passes or timeout ms elapse, in which case reject.
function pollUntil(fn, predicate, options) {
// options: { interval = 100, timeout = 5000 }
// returns a promise that resolves with the first result that passes
// `predicate`, or rejects on timeout.
}
// Poll a job's status every 500ms until it's complete, giving up after 30s:
const job = await pollUntil(
() => api.getJob(id),
(job) => job.status === 'complete',
{ interval: 500, timeout: 30000 },
);
// Wait until a value is truthy:
await pollUntil(() => cache.get('key'), Boolean, { interval: 100, timeout: 2000 });
fn, test its result with predicate; resolve if it passes.interval between polls (unlike backoff, which grows).timeout ms pass without a passing result, reject. This is what stops an infinite loop.fn itself rejects, reject; don't swallow it and keep polling.