let timerId;
function task() {
console.log("doing task...");
timerId = setTimeout(task, 1000);
}
function start() {
if (!timerId) task();
}
function stop() {
clearTimeout(timerId);
timerId = null;
}
start();
stop();
五分钟刷新请求
import { useEffect, useRef, useState, useCallback } from "react";
type UsePollingRequestOptions<T> = {
request: () => Promise<T>,
interval?: number,
immediate?: boolean,
};
export function usePollingRequest<T = unknown>({
request,
interval = 5 * 60 * 1000,
immediate = true,
}: UsePollingRequestOptions<T>) {
const [data, setData] = (useState < T) | (null > null);
const [loading, setLoading] = useState(false);
const timerRef = (useRef < number) | (null > null);
const isMounted = useRef(true);
const fetchData = useCallback(async () => {
setLoading(true);
try {
const result = await request();
if (isMounted.current) {
setData(result);
}
} catch (error) {
console.error("Polling request error:", error);
} finally {
if (isMounted.current) {
setLoading(false);
timerRef.current = window.setTimeout(fetchData, interval);
}
}
}, [request, interval]);
const stop = () => {
if (timerRef.current) {
clearTimeout(timerRef.current);
timerRef.current = null;
}
};
const start = () => {
stop();
fetchData();
};
useEffect(() => {
if (immediate) {
fetchData();
} else {
timerRef.current = window.setTimeout(fetchData, interval);
}
return () => {
isMounted.current = false;
stop();
};
}, [fetchData, immediate, interval]);
return {
data,
loading,
refresh: fetchData,
stop,
start,
};
}
使用案例
const fetchData = () => axios.get('/api/data').then(res => res.data);
function MyComponent() {
const { data, loading, refresh, stop, start } = usePollingRequest({
request: fetchData,
interval: 5 * 60 * 1000,
immediate: true,
});
return (
<div>
{loading && <p>加载中...</p>}
<pre>{JSON.stringify(data, null, 2)}</pre>
<button onClick={refresh}>手动刷新</button>
<button onClick={stop}>停止</button>
<button onClick={start}>重新开始</button>
</div>
);
}
JavaScript 定时器的精度受 浏览器调度 和 主线程阻塞 影响。
这意味着 两次请求之间的间隔是 5 分钟 + 3 秒,会随着网络波动慢慢偏移。
### 使用时间差纠正偏移量:
const nextTick = () => {
const now = Date.now();
const offset = now % interval;
const delay = interval - offset;
setTimeout(() => {
fetchData();
nextTick();
}, delay);
改进后的
import { useEffect, useRef, useState, useCallback } from 'react';
type UsePollingRequestOptions<T> = {
request: () => Promise<T>;
interval?: number;
immediate?: boolean;
alignToInterval?: boolean;
};
export function usePollingRequest<T = unknown>({
request,
interval = 5 * 60 * 1000,
immediate = true,
alignToInterval = true,
}: UsePollingRequestOptions<T>) {
const [data, setData] = useState<T | null>(null);
const [loading, setLoading] = useState(false);
const timerRef = useRef<number | null>(null);
const isMounted = useRef(true);
const fetchData = useCallback(async () => {
setLoading(true);
try {
const result = await request();
if (isMounted.current) {
setData(result);
}
} catch (err) {
console.error('Polling error:', err);
} finally {
setLoading(false);
}
}, [request]);
const scheduleNext = useCallback(() => {
const now = Date.now();
const delay = alignToInterval
? interval - (now % interval)
: interval;
timerRef.current = window.setTimeout(async () => {
await fetchData();
scheduleNext();
}, delay);
}, [fetchData, interval, alignToInterval]);
const start = useCallback(() => {
stop();
if (immediate) {
fetchData().then(scheduleNext);
} else {
scheduleNext();
}
}, [fetchData, scheduleNext, immediate]);
const stop = () => {
if (timerRef.current) {
clearTimeout(timerRef.current);
timerRef.current = null;
}
};
useEffect(() => {
isMounted.current = true;
start();
return () => {
isMounted.current = false;
stop();
};
}, [start]);
return {
data,
loading,
refresh: fetchData,
start,
stop,
};
}