usePolled reports when a request is in flight (through a new onBusy of the poller) and holds it on for at least 0.4 seconds, so a quick answer still reads as a refresh. The picker swaps its clock for the spinner, in the same box so its width stays, and the spinner stands still where motion is off. Every page with a picker passes it; a page with several polled requests shows it while any of them runs. The Jobs page fetches through shared components and has none yet.
61 lines
1.7 KiB
TypeScript
61 lines
1.7 KiB
TypeScript
// 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<T>(opts: {
|
|
fetch: (signal: AbortSignal) => Promise<T>;
|
|
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,
|
|
};
|
|
}
|