Files
photo-live/apps/admin/src/utils/system/syncPolling.ts
T
2026-09-20 20:26:53 +08:00

93 lines
2.7 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 用户同步页轮询逻辑(纯函数/可注入依赖,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
}
}
}