// The request discipline behind a polled page, kept free of Preact so it can // be tested on its own. `tick` fetches unless a request is already in flight, // so a slow answer is not overtaken by the next tick. `refetch` drops the // request in flight and asks again, for a refresh that follows a change. // `stop` drops the request in flight. An answer from a dropped request never // reaches `onData` or `onError`, so an older response cannot overwrite a // newer one or land on a page that is gone. `onBusy` tells when a request goes // in flight and when none is any more, however it ended, so a page can show // that it is refreshing. export interface Poller { tick(): void; refetch(): void; stop(): void; } export function createPoller(opts: { fetch: (signal: AbortSignal) => Promise; onData: (data: T) => void; onError: (error: unknown) => void; onBusy?: (busy: boolean) => void; }): Poller { let current: { controller: AbortController } | null = null; function start() { const run = { controller: new AbortController() }; current = run; opts.onBusy?.(true); opts.fetch(run.controller.signal).then( (data) => { if (current !== run) return; current = null; opts.onBusy?.(false); opts.onData(data); }, (error: unknown) => { if (current !== run) return; current = null; opts.onBusy?.(false); opts.onError(error); }, ); } function stop() { if (current === null) return; current.controller.abort(); current = null; opts.onBusy?.(false); } return { tick() { if (current === null) start(); }, refetch() { stop(); start(); }, stop, }; }