如何基于TanStack React Query实现异步任务状态实时监控?
问题场景与需求
场景说明
点击事件触发handleDeleteQAPairBatch异步函数,代码如下:
async function handleDeleteQAPairBatch() { if (selectedPairList.length < 1) return toast.error("請先選擇問答內容"); const batchPack = selectedPairList.map((item) => ({ op: "delete", value: item, })); const { result: deleteBatchResult, data } = await UpdateQuestionAnsweringPair(batchPack); const urlArray = data.split("/"); const params = urlArray[urlArray.length - 1]; const jobId = params.split("?")[0]; try { const jobStatus = await queryClient.fetchQuery({ queryKey: ["jobStatus"], queryFn: () => GetQAUpdateJobStatus(jobId), staleTime: 0, }); console.log(jobStatus); setJobStatusData(jobStatus); } catch (error) { console.log(error); } if (deleteBatchResult) { toast.success("問答內容刪除成功"); setSelectedPairList([]); } }
函数执行逻辑:
- 调用异步函数
UpdateQuestionAnsweringPair请求API执行数据更新; - 接口返回包含
jobId参数的URL; - 提取
jobId后调用GetQAUpdateJobStatus接口获取任务状态,返回数据结构示例:
{ "createdDateTime":"2024-03-04T06:19:29+00:00", "expirationDateTime":"2024-03-04T12:19:29+00:00", "jobId":"8012fc66-0855-4ef0-b692-0b31263e59bf", "lastUpdatedDateTime":"2024-03-04T06:19:29+00:00", "status":"notStarted", "resultUrl":null }
任务完成后状态更新为:
{ "createdDateTime": "2024-03-04T06:19:29+00:00", "expirationDateTime": "2024-03-04T12:19:29+00:00", "jobId": "8012fc66-0855-4ef0-b692-0b31263e59bf", "lastUpdatedDateTime": "2024-03-04T06:19:35+00:00", "status": "succeeded", "resultUrl": "/knowledgebases/MiniTai" }
需求
监控GetQAUpdateJobStatus接口返回的status字段变化,以此执行对应业务逻辑并更新页面。
遇到的问题
useQuery无法在异步函数内直接使用,改用queryClient.fetchQuery仅能执行一次,无法同步获取更新后的任务状态。
现有替代方案
采用带轮询的useQuery实现,但逻辑不够直观,代码如下:
const { data: JobStatus } = useQuery({ queryKey: ["JobStatus"], queryFn: async () => { if (currentJobID) { try { const result = await GetQAUpdateJobStatus(currentJobID); if (result.status === "succeeded") { setCurrentJobID(null); queryClient.refetchQueries({ queryKey: ["QAPairList"] }); } return result; } catch (error) { console.log(error); } } else { return null; } }, refetchInterval: 1 * 1000, refetchIntervalInBackground: true, });
问询
是否有更合适的TanStack React Query方案实现该需求?
解决方案
针对异步任务状态监控场景,TanStack React Query有几种更优雅的方案,比固定轮询更灵活高效:
1. 基于状态判断的动态轮询
给refetchInterval设置函数,仅当任务未完成时开启轮询,任务结束后自动停止,避免无效请求:
const { data: jobStatus } = useQuery({ queryKey: ["jobStatus", currentJobID], // 将currentJobID加入queryKey,确保jobId变化时重新触发查询 queryFn: () => GetQAUpdateJobStatus(currentJobID), enabled: !!currentJobID, // 仅当存在currentJobID时执行查询 refetchInterval: (data) => { // 仅当状态未处于成功/失败时,继续轮询 if (data?.status !== "succeeded" && data?.status !== "failed") { return 1000; } return false; // 停止轮询 }, onSuccess: (data) => { if (data.status === "succeeded") { setCurrentJobID(null); queryClient.refetchQueries({ queryKey: ["QAPairList"] }); toast.success("任务执行完成"); } else if (data.status === "failed") { toast.error("任务执行失败"); setCurrentJobID(null); } }, });
这种方式既保留轮询逻辑,又能根据任务状态动态启停,代码逻辑更清晰。
2. 使用useMutation结合手动查询重试
将删除操作封装成useMutation,在onSuccess中手动触发任务状态查询,通过循环重试直到任务完成:
const queryClient = useQueryClient(); const [deleteQAPairs] = useMutation({ mutationFn: (batchPack) => UpdateQuestionAnsweringPair(batchPack), onSuccess: async (response) => { const urlArray = response.data.split("/"); const params = urlArray[urlArray.length - 1]; const jobId = params.split("?")[0]; // 循环查询任务状态直到完成 const pollJobStatus = async () => { const status = await queryClient.fetchQuery({ queryKey: ["jobStatus", jobId], queryFn: () => GetQAUpdateJobStatus(jobId), staleTime: 0, }); if (status.status === "notStarted" || status.status === "running") { setTimeout(pollJobStatus, 1000); } else { if (status.status === "succeeded") { toast.success("問答內容刪除成功"); queryClient.refetchQueries({ queryKey: ["QAPairList"] }); } else { toast.error("刪除任務執行失敗"); } setSelectedPairList([]); } }; pollJobStatus(); }, onError: () => { toast.error("刪除請求發送失敗"); }, }); // 触发删除的函数 const handleDeleteQAPairBatch = () => { if (selectedPairList.length < 1) { toast.error("請先選擇問答內容"); return; } const batchPack = selectedPairList.map(item => ({ op: "delete", value: item })); deleteQAPairs(batchPack); };
这种方式将删除和状态查询逻辑整合到useMutation生命周期中,符合React Query设计模式,无需额外维护currentJobID状态,逻辑更内聚。
3. 使用invalidateQueries手动触发更新
若不想用轮询,可在获取初始状态后,定时调用invalidateQueries让React Query自动重新查询,直到任务完成:
const { data: jobStatus } = useQuery({ queryKey: ["jobStatus", currentJobID], queryFn: () => GetQAUpdateJobStatus(currentJobID), enabled: !!currentJobID, staleTime: 0, }); useEffect(() => { if (!currentJobID) return; const interval = setInterval(() => { if (jobStatus?.status === "succeeded" || jobStatus?.status === "failed") { clearInterval(interval); if (jobStatus.status === "succeeded") { setCurrentJobID(null); queryClient.refetchQueries({ queryKey: ["QAPairList"] }); } } else { queryClient.invalidateQueries({ queryKey: ["jobStatus", currentJobID] }); } }, 1000); return () => clearInterval(interval); }, [currentJobID, jobStatus, queryClient]);
这种方式通过手动触发缓存失效更新状态,适合需要精细控制查询时机的场景。
内容的提问来源于stack exchange,提问作者Samuel Pedro Chao
相关产品推荐
相关产品推荐

