"use client"; import { useCallback, useEffect, useReducer, useRef } from "react"; import { api } from "@/lib/api/client"; import { initialPollState, isTerminal, narrowJob, reducePoll, type PollState, } from "./job"; const POLL_INTERVAL_MS = 1500; export interface UseJobPoll extends PollState { // 开始轮询某个 job(替换正在轮询的 job)。 poll: (jobId: string) => void; // 复位到初始(停止当前轮询)。 reset: () => void; } // 轮询 GET /jobs/{job_id}:setTimeout 循环,done/failed 终态停止,卸载清理。 // 纯状态机在 job.ts(可单测);本 hook 只负责定时器 + fetch 编排。 export function useJobPoll(): UseJobPoll { const [state, dispatch] = useReducer(reducePoll, initialPollState); const timerRef = useRef | null>(null); const activeRef = useRef(null); const clearTimer = useCallback((): void => { if (timerRef.current !== null) { clearTimeout(timerRef.current); timerRef.current = null; } }, []); const tick = useCallback( async (jobId: string): Promise => { if (activeRef.current !== jobId) return; try { const { data, error } = await api.GET("/jobs/{job_id}", { params: { path: { job_id: jobId } }, }); if (activeRef.current !== jobId) return; // 中途切换/卸载。 if (error || !data) { dispatch({ type: "fail", error: "轮询任务状态失败" }); return; } const job = narrowJob(data); dispatch({ type: "tick", job }); if (isTerminal(job.status)) { activeRef.current = null; return; } timerRef.current = setTimeout(() => void tick(jobId), POLL_INTERVAL_MS); } catch (err: unknown) { if (activeRef.current !== jobId) return; const message = err instanceof Error ? err.message : "未知网络错误"; dispatch({ type: "fail", error: message }); } }, [], ); const poll = useCallback( (jobId: string): void => { clearTimer(); activeRef.current = jobId; void tick(jobId); }, [clearTimer, tick], ); const reset = useCallback((): void => { clearTimer(); activeRef.current = null; }, [clearTimer]); // 卸载清理。 useEffect(() => { return () => { if (timerRef.current !== null) clearTimeout(timerRef.current); activeRef.current = null; }; }, []); return { ...state, poll, reset }; }