fix(frontend): RefineView 取消在途 refine——AbortController 防段切换竞态(CR-H12)

This commit is contained in:
Yaojia Wang
2026-07-08 11:08:21 +02:00
parent 5665cc9a16
commit 5c02792e5f
3 changed files with 65 additions and 7 deletions

View File

@@ -1,6 +1,6 @@
"use client";
import { useCallback, useState } from "react";
import { useCallback, useRef, useState } from "react";
import { api } from "@/lib/api/client";
import { useToast } from "@/components/Toast";
@@ -24,6 +24,8 @@ export interface UseRefine {
segment: string,
instruction?: string,
) => Promise<RefineResult | null>;
// 取消在途回炉(段切换/卸载时调用)——不触碰 state安全用于 effect 清理。
abort: () => void;
reset: () => void;
}
@@ -32,10 +34,15 @@ export interface UseRefine {
export function useRefine(): UseRefine {
const [status, setStatus] = useState<RefineStatus>("idle");
const [result, setResult] = useState<RefineResult | null>(null);
const controllerRef = useRef<AbortController | null>(null);
const toast = useToast();
const refine = useCallback<UseRefine["refine"]>(
async (projectId, chapterNo, segment, instruction) => {
// 取消上一次在途请求,换新 controller段切换竞态防护CR-H12
controllerRef.current?.abort();
const controller = new AbortController();
controllerRef.current = controller;
setStatus("refining");
setResult(null);
try {
@@ -46,8 +53,11 @@ export function useRefine(): UseRefine {
path: { project_id: projectId, chapter_no: chapterNo },
},
body: buildRefineRequest(segment, instruction),
signal: controller.signal,
},
);
// 已被后续请求/卸载中止:丢弃迟到响应,不写状态。
if (controller.signal.aborted) return null;
if (error || !data) {
setStatus("error");
const code = errorCode(error);
@@ -67,6 +77,8 @@ export function useRefine(): UseRefine {
setStatus("done");
return outcome;
} catch {
// 主动中止abort触发的异常不该报错 toast。
if (controller.signal.aborted) return null;
setStatus("error");
toast("回炉请求异常,请检查网络。", "error");
return null;
@@ -75,10 +87,18 @@ export function useRefine(): UseRefine {
[toast],
);
// 仅取消在途请求,不触碰 state——可安全用于 effect 清理 / 卸载。
const abort = useCallback((): void => {
controllerRef.current?.abort();
controllerRef.current = null;
}, []);
const reset = useCallback((): void => {
controllerRef.current?.abort();
controllerRef.current = null;
setStatus("idle");
setResult(null);
}, []);
return { status, result, refine, reset };
return { status, result, refine, abort, reset };
}