fix(admin): useTable 请求竞态守卫(旧请求不清 loading/不写错误态)
- fetchData 加请求序号令牌 fetchSeq:请求被新请求取代后,旧响应不再 改动任何状态——修复两类竞态:旧请求取消分支把 loading 提前清成 idle、 旧请求失败分支把新请求在途数据清空并置 error - 与 dashboard reqSeq 修法同口径(id===seq 才写数据/清 loading), 供访客列表等分页筛选页复用 Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
@@ -197,6 +197,9 @@ function useTableImpl<TApiFn extends (params: any) => Promise<any>>(
|
|||||||
// 请求取消控制器
|
// 请求取消控制器
|
||||||
let abortController: AbortController | null = null
|
let abortController: AbortController | null = null
|
||||||
|
|
||||||
|
// 请求序号令牌(竞态守卫):请求被更新的请求取代后,不得再写数据/清 loading/置错误态
|
||||||
|
let fetchSeq = 0
|
||||||
|
|
||||||
// 缓存清理定时器
|
// 缓存清理定时器
|
||||||
let cacheCleanupTimer: NodeJS.Timeout | null = null
|
let cacheCleanupTimer: NodeJS.Timeout | null = null
|
||||||
|
|
||||||
@@ -288,6 +291,7 @@ function useTableImpl<TApiFn extends (params: any) => Promise<any>>(
|
|||||||
// 创建新的取消控制器
|
// 创建新的取消控制器
|
||||||
const currentController = new AbortController()
|
const currentController = new AbortController()
|
||||||
abortController = currentController
|
abortController = currentController
|
||||||
|
const currentSeq = ++fetchSeq
|
||||||
|
|
||||||
// 状态机:进入 loading 状态
|
// 状态机:进入 loading 状态
|
||||||
loadingState.value = 'loading'
|
loadingState.value = 'loading'
|
||||||
@@ -391,15 +395,24 @@ function useTableImpl<TApiFn extends (params: any) => Promise<any>>(
|
|||||||
|
|
||||||
return standardResponse
|
return standardResponse
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
// 竞态守卫:已被更新请求取代的旧请求不再改动任何状态
|
||||||
|
// (防旧 finally 提前清 loading、旧失败响应清空新请求的数据)
|
||||||
|
const superseded = currentSeq !== fetchSeq
|
||||||
if (err instanceof Error && err.message === '请求已取消') {
|
if (err instanceof Error && err.message === '请求已取消') {
|
||||||
// 请求被取消,回到 idle 状态
|
// 请求被取消,回到 idle 状态(仅当它仍是最新请求,如显式 cancelRequest)
|
||||||
loadingState.value = 'idle'
|
if (!superseded) loadingState.value = 'idle'
|
||||||
return { records: [], total: 0, current: 1, size: 10 }
|
return { records: [], total: 0, current: 1, size: 10 }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!superseded) {
|
||||||
// 状态机:请求失败,进入 error 状态
|
// 状态机:请求失败,进入 error 状态
|
||||||
loadingState.value = 'error'
|
loadingState.value = 'error'
|
||||||
data.value = []
|
data.value = []
|
||||||
|
}
|
||||||
|
if (superseded) {
|
||||||
|
// 旧请求的失败交给新请求兜底,不触发错误回调
|
||||||
|
throw err instanceof Error ? err : new Error(String(err))
|
||||||
|
}
|
||||||
const tableError = handleError(err, '获取表格数据失败')
|
const tableError = handleError(err, '获取表格数据失败')
|
||||||
throw tableError
|
throw tableError
|
||||||
} finally {
|
} finally {
|
||||||
|
|||||||
Reference in New Issue
Block a user