You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何基于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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.28 15:24:51