Files
crawler-plugin/frontend-vue/src/shared/page-cleanup-registry.ts
T
huangzd1997 bb2f200d76 task-89: 清理页面卸载时的所有 timer、请求和临时 URL
新增纯 TS 页面卸载清理注册表 page-cleanup-registry:统一登记 blob
临时 URL(dispose 时 revokeUrl 释放)、AbortController(abort)与
timer 清理委托(clearTimers)。dispose 幂等且后注册拒绝并计数;
revokeUrl 抛错不中断其余条目;maxTrackedUrls 上限只保留最近注册的
URL。8 个测试覆盖默认、批量、幂等、空、单元素、上限驱逐、非法 hooks
与 revoke 故障恢复。
2026-08-30 22:57:04 +08:00

117 lines
3.0 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.
/**
* 页面卸载清理注册表(Task 89)。
*
* 统一管理页面级需在卸载时释放的资源:
* - 临时 URLcreateObjectURL 产生的 blob URL 通过 revokeUrl 释放;
* - 请求:AbortController 在卸载时 abortregisterRequestController);
* - timerdispose 时委托 hooks.clearTimers()(配合 categorized-timers
* 的 clearScope 全量清理)。
*
* dispose 幂等:第二次及以后的调用不再重复清理;dispose 后注册被拒绝
* 并计数。revokeUrl 抛错不中断其余条目的清理;maxTrackedUrls(默认不
* 限)用于限制同一页面累计注册的 URL 数量,超过时只保留最近注册的。
*/
export interface PageCleanupRegistryHooks {
/** 卸载时清空全部 timer */
clearTimers: () => void
/** 卸载时释放单个 blob URL */
revokeUrl: (url: string) => void
}
export interface PageCleanupRegistryOptions {
/** 累计跟踪的 URL 上限(0 表示不限制),超过时只保留最近注册的 */
maxTrackedUrls?: number
}
export function createPageCleanupRegistry(
hooks: PageCleanupRegistryHooks,
options: PageCleanupRegistryOptions = {},
) {
if (typeof hooks?.clearTimers !== 'function') {
throw new Error('clearTimers 必须是函数')
}
if (typeof hooks?.revokeUrl !== 'function') {
throw new Error('revokeUrl 必须是函数')
}
const maxTrackedUrls = options.maxTrackedUrls ?? 0
const urls: string[] = []
const controllers: AbortController[] = []
let disposed = false
let disposeCount = 0
let rejectedCount = 0
function registerObjectUrl(url: string): string {
if (disposed) {
rejectedCount += 1
return url
}
if (!url) return url
urls.push(url)
if (maxTrackedUrls > 0 && urls.length > maxTrackedUrls) {
urls.splice(0, urls.length - maxTrackedUrls)
}
return url
}
function registerRequestController(controller: AbortController) {
if (disposed) {
rejectedCount += 1
return
}
controllers.push(controller)
}
function dispose(): number {
if (disposed) {
return 0
}
disposed = true
let released = 0
for (const url of urls) {
try {
hooks.revokeUrl(url)
} catch {
/* 单个 URL 释放失败不中断其余清理 */
}
released += 1
}
urls.length = 0
for (const controller of controllers) {
try {
controller.abort()
} catch {
/* abort 失败不中断其余清理 */
}
released += 1
}
controllers.length = 0
hooks.clearTimers()
released += 1
disposeCount += 1
return released
}
return {
get trackedUrlCount() {
return urls.length
},
get requestCount() {
return controllers.length
},
get disposed() {
return disposed
},
get disposeCount() {
return disposeCount
},
get rejectedCount() {
return rejectedCount
},
registerObjectUrl,
registerRequestController,
dispose,
}
}
export type PageCleanupRegistry = ReturnType<typeof createPageCleanupRegistry>