|
| 1 | +import { useCallback, useEffect, useRef } from 'react' |
| 2 | +import type { PollingConfig, PollingControls } from './types' |
| 3 | +import { usePollingControl } from './usePollingControl' |
| 4 | + |
| 5 | +export const usePolling = <T extends (...args: never[]) => Promise<void>>( |
| 6 | + callback: T, |
| 7 | + timeout: number, |
| 8 | + config: PollingConfig = {} |
| 9 | +): PollingControls => { |
| 10 | + const timerIdRef = useRef<ReturnType<typeof window.requestAnimationFrame>>() |
| 11 | + const callbackRef = useRef<T>(callback) |
| 12 | + const pollTimerRef = useRef<number>() |
| 13 | + |
| 14 | + const { isPausedRef, ...controls } = usePollingControl() |
| 15 | + |
| 16 | + const { onError } = config |
| 17 | + const interval = timeout < 0 ? 0 : timeout |
| 18 | + |
| 19 | + useEffect(() => { |
| 20 | + callbackRef.current = callback |
| 21 | + }, [callback]) |
| 22 | + |
| 23 | + const loop = useCallback( |
| 24 | + async (time: number): Promise<void> => { |
| 25 | + if (!pollTimerRef.current) { |
| 26 | + pollTimerRef.current = time |
| 27 | + } |
| 28 | + const elapsed = time - pollTimerRef.current |
| 29 | + if (elapsed >= interval) { |
| 30 | + if (!isPausedRef.current) { |
| 31 | + await callbackRef.current().catch(() => { |
| 32 | + onError?.() |
| 33 | + }) |
| 34 | + pollTimerRef.current = undefined |
| 35 | + } |
| 36 | + } |
| 37 | + timerIdRef.current = window.requestAnimationFrame(loop) |
| 38 | + }, |
| 39 | + [interval, isPausedRef, onError] |
| 40 | + ) |
| 41 | + |
| 42 | + useEffect(() => { |
| 43 | + timerIdRef.current = window.requestAnimationFrame(loop) |
| 44 | + |
| 45 | + return () => { |
| 46 | + if (timerIdRef.current) { |
| 47 | + window.cancelAnimationFrame(timerIdRef.current) |
| 48 | + } |
| 49 | + } |
| 50 | + }, [loop]) |
| 51 | + |
| 52 | + return controls |
| 53 | +} |
0 commit comments