618d1d796c
Co-Authored-By: Claude Code <noreply@anthropic.com>
93 lines
2.7 KiB
TypeScript
93 lines
2.7 KiB
TypeScript
/**
|
||
* 用户同步页轮询逻辑(纯函数/可注入依赖,vitest 用 fake timers 覆盖)
|
||
*
|
||
* - waitSyncTerminal:手动补拉 202 受理后等待批次离开 running(间隔轮询 +
|
||
* 超时上限;终态即 resolve,避免页面无限转圈)
|
||
* - createAutoRefresh:页面打开期间的定时状态刷新(10s);立即执行一次 +
|
||
* 间隔触发 + 重入保护(慢响应不堆叠)+ 幂等 start/stop(组件
|
||
* onUnmounted 清理,防路由离开后仍在请求)
|
||
*/
|
||
|
||
export interface WaitSyncOptions {
|
||
/** 轮询间隔(默认 1.5s:批次通常秒级完成) */
|
||
intervalMs?: number
|
||
/** 总超时(默认 45s:超过视为异常,交调用方展示错误) */
|
||
timeoutMs?: number
|
||
}
|
||
|
||
const DEFAULT_INTERVAL_MS = 1500
|
||
const DEFAULT_TIMEOUT_MS = 45_000
|
||
|
||
function delay(ms: number): Promise<void> {
|
||
return new Promise((resolve) => setTimeout(resolve, ms))
|
||
}
|
||
|
||
/**
|
||
* 轮询同步状态直到批次离开 running(首次查询即终态则立刻 resolve)。
|
||
* 超时 reject(Error.message = 'timeout');fetchStatus 的网络错误向上抛。
|
||
*/
|
||
export function waitSyncTerminal(
|
||
fetchStatus: () => Promise<Api.UserSync.Status>,
|
||
options: WaitSyncOptions = {}
|
||
): Promise<Api.UserSync.Status> {
|
||
const intervalMs = options.intervalMs ?? DEFAULT_INTERVAL_MS
|
||
const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS
|
||
const deadline = Date.now() + timeoutMs
|
||
return (async function poll(): Promise<Api.UserSync.Status> {
|
||
const status = await fetchStatus()
|
||
if (status.state !== 'running') return status
|
||
if (Date.now() >= deadline) {
|
||
throw new Error('timeout')
|
||
}
|
||
await delay(intervalMs)
|
||
return poll()
|
||
})()
|
||
}
|
||
|
||
export interface AutoRefreshHandle {
|
||
/** 开始(立即执行一次 + 间隔触发;重复调用幂等) */
|
||
start: () => void
|
||
/** 停止并清理定时器(重复调用幂等) */
|
||
stop: () => void
|
||
/** 是否在刷新周期内 */
|
||
isRunning: () => boolean
|
||
}
|
||
|
||
/**
|
||
* 周期刷新:start 立即触发一次;执行中的回调未完成时不堆叠下一次。
|
||
* 返回的 handle 由组件持有,onUnmounted 时 stop()。
|
||
*/
|
||
export function createAutoRefresh(
|
||
fn: () => void | Promise<unknown>,
|
||
intervalMs = 10_000
|
||
): AutoRefreshHandle {
|
||
let timer: ReturnType<typeof setInterval> | null = null
|
||
let inFlight = false
|
||
|
||
async function run(): Promise<void> {
|
||
if (inFlight) return
|
||
inFlight = true
|
||
try {
|
||
await fn()
|
||
} finally {
|
||
inFlight = false
|
||
}
|
||
}
|
||
|
||
return {
|
||
start() {
|
||
if (timer !== null) return
|
||
void run()
|
||
timer = setInterval(() => void run(), intervalMs)
|
||
},
|
||
stop() {
|
||
if (timer === null) return
|
||
clearInterval(timer)
|
||
timer = null
|
||
},
|
||
isRunning() {
|
||
return timer !== null
|
||
}
|
||
}
|
||
}
|