fix(marketplace): stale detail response guard (pr_agent #444 round 3)

跟 round 2 同类 race condition:openDetail 用户快速点两张卡片时,先点 A 的
detail response 可能晚于 B 到达 → 覆盖 B 的 detail / Install / Like 作用错对象。

修:复用 useRef seq token 模式,detailSeqRef 单调递增,response 比较 seq 丢
弃 stale。逻辑跟 refresh() 完全对称。
This commit is contained in:
baiqing committed 2026-05-15 14:52:22 +08:00
1 parent 4af7fec82f
commit 9e119e2a2f
1 file changed
+6 -2
+6 -2
View File
@@ -53,9 +53,10 @@ export function Marketplace() {
return () => window.clearTimeout(id);
}, [query]);
// 单调递增 seq 防 stale 响应覆盖:用户快速改 query 时旧请求的 response
// 单调递增 seq 防 stale 响应覆盖:用户快速改 query / 切换 pack 时旧请求 response
// 可能晚于新请求到达,比较 seq 丢弃过期结果。
const reqSeqRef = useRef(0);
const detailSeqRef = useRef(0);
const refresh = useCallback(async () => {
const seq = ++reqSeqRef.current;
setLoading(true);
@@ -78,18 +79,21 @@ export function Marketplace() {
}, [refresh]);
const openDetail = async (id: string) => {
const seq = ++detailSeqRef.current;
setSelectedId(id);
setDetail(null);
setDetailLoading(true);
try {
const d = await fetchMarketplaceDetail(id);
if (seq !== detailSeqRef.current) return; // stale: 用户已切到另一个 pack
setDetail(d);
} catch (error) {
if (seq !== detailSeqRef.current) return;
console.error('[marketplace] detail failed', error);
setActionMsg({ kind: 'err', text: t('marketplace.errors.detail', { err: errorMessage(error) }) });
setSelectedId(null);
} finally {
setDetailLoading(false);
if (seq === detailSeqRef.current) setDetailLoading(false);
}
};