dc6e8924a9
QueryAsin / Withdraw / PatrolDelete 三页逐字相同的 startHistoryPolling / stopHistoryPolling 收敛为 shared/composables/useHistoryPolling:定时器走各页 categorized-timers(category 固定 history-poll),间隔默认主轮询的 2 倍。 categorized-timers 补 CategorizedTimers 类型导出;补注入式假定时器单测 5 例。 净减约 40 行;vue-tsc 构建与 695 个前端单测通过。
116 lines
3.3 KiB
TypeScript
116 lines
3.3 KiB
TypeScript
type TimerKind = 'timeout' | 'interval'
|
|
type TimerCancel = () => void
|
|
|
|
interface TimerEntry {
|
|
id: number
|
|
kind: TimerKind
|
|
category: string
|
|
cancel?: TimerCancel
|
|
}
|
|
|
|
const timerBuckets = new Map<string, Map<number, TimerEntry>>()
|
|
|
|
function bucketOf(category: string) {
|
|
let bucket = timerBuckets.get(category)
|
|
if (!bucket) {
|
|
bucket = new Map<number, TimerEntry>()
|
|
timerBuckets.set(category, bucket)
|
|
}
|
|
return bucket
|
|
}
|
|
|
|
function removeTimer(category: string, id: number) {
|
|
const bucket = timerBuckets.get(category)
|
|
if (!bucket) return
|
|
bucket.delete(id)
|
|
if (!bucket.size) timerBuckets.delete(category)
|
|
}
|
|
|
|
export function setCategorizedTimeout(category: string, handler: () => void, delayMs: number) {
|
|
const id = window.setTimeout(() => {
|
|
removeTimer(category, id)
|
|
handler()
|
|
}, delayMs)
|
|
bucketOf(category).set(id, { id, kind: 'timeout', category })
|
|
return id
|
|
}
|
|
|
|
export function setCategorizedInterval(category: string, handler: () => void, delayMs: number) {
|
|
const id = window.setInterval(handler, delayMs)
|
|
bucketOf(category).set(id, { id, kind: 'interval', category })
|
|
return id
|
|
}
|
|
|
|
export function clearCategorizedTimer(category: string, id: number | null | undefined) {
|
|
if (id == null) return
|
|
const bucket = timerBuckets.get(category)
|
|
const entry = bucket?.get(id)
|
|
if (entry?.kind === 'interval') window.clearInterval(id)
|
|
else window.clearTimeout(id)
|
|
entry?.cancel?.()
|
|
removeTimer(category, id)
|
|
}
|
|
|
|
export function clearCategorizedTimers(category: string) {
|
|
const bucket = timerBuckets.get(category)
|
|
if (!bucket) return
|
|
for (const entry of bucket.values()) {
|
|
if (entry.kind === 'interval') window.clearInterval(entry.id)
|
|
else window.clearTimeout(entry.id)
|
|
entry.cancel?.()
|
|
}
|
|
timerBuckets.delete(category)
|
|
}
|
|
|
|
export function sleepWithCategory(category: string, delayMs: number) {
|
|
return new Promise<void>((resolve) => {
|
|
const id = window.setTimeout(() => {
|
|
removeTimer(category, id)
|
|
resolve()
|
|
}, delayMs)
|
|
bucketOf(category).set(id, { id, kind: 'timeout', category, cancel: resolve })
|
|
})
|
|
}
|
|
|
|
export function clearTimerScope(scope: string) {
|
|
const prefix = `${scope}:`
|
|
for (const category of Array.from(timerBuckets.keys())) {
|
|
if (category === scope || category.startsWith(prefix)) {
|
|
clearCategorizedTimers(category)
|
|
}
|
|
}
|
|
}
|
|
|
|
export function createCategorizedTimers(scope: string) {
|
|
const key = (category: string) => `${scope}:${category}`
|
|
return {
|
|
setTimeout(category: string, handler: () => void, delayMs: number) {
|
|
return setCategorizedTimeout(key(category), handler, delayMs)
|
|
},
|
|
setInterval(category: string, handler: () => void, delayMs: number) {
|
|
return setCategorizedInterval(key(category), handler, delayMs)
|
|
},
|
|
clearTimer(category: string, id: number | null | undefined) {
|
|
clearCategorizedTimer(key(category), id)
|
|
},
|
|
clearCategory(category: string) {
|
|
clearCategorizedTimers(key(category))
|
|
},
|
|
clearScope() {
|
|
clearTimerScope(scope)
|
|
},
|
|
sleep(category: string, delayMs: number) {
|
|
return sleepWithCategory(key(category), delayMs)
|
|
},
|
|
}
|
|
}
|
|
|
|
export type CategorizedTimers = ReturnType<typeof createCategorizedTimers>
|
|
|
|
export function getCategorizedTimerStats() {
|
|
return Array.from(timerBuckets.entries()).map(([category, bucket]) => ({
|
|
category,
|
|
count: bucket.size,
|
|
}))
|
|
}
|