task-81: 建立前端任务轮询请求量、响应体大小和页面内存基线

新增纯 TS 轮询基线组件 task-polling-baseline:累计轮询请求数、每次
批量响应的总字节与单次最大响应、按条目数/字节双上限的有界内存缓存
(超限驱逐最旧条目),失败路径零状态变更。useTaskProgressLoop 新增
可选 onBaseline 选项接入基线(默认零行为变化)。测试用 Node 24 原生
type-stripping + node:test 运行(npm test),8 个用例覆盖默认路径、
批量、幂等、空输入、单元素、限流溢出、非法输入与失败恢复。
This commit is contained in:
2026-08-30 22:38:51 +08:00
parent 0ce664d2b9
commit 9bf25765dd
4 changed files with 321 additions and 1 deletions
@@ -1,6 +1,10 @@
import { onBeforeUnmount, ref, watch, type Ref } from 'vue'
import { getTaskPollIntervalMs } from '@/shared/task-progress-config'
import { createCategorizedTimers } from '@/shared/utils/categorized-timers'
import {
createTaskPollingBaseline,
type TaskPollingBaseline,
} from '@/shared/task-polling-baseline'
/**
* 通用任务进度轮询组合式函数。
@@ -46,6 +50,12 @@ export interface TaskProgressLoopOptions<TDetail> {
onError?: (error: unknown) => void
/** 自定义轮询间隔;默认根据 document.visibilityState 自适应(5s/30s */
getIntervalMs?: () => number
/**
* 可选轮询基线统计(Task 81):传入后每轮请求与响应都会被记录到基线,
* 并在每次 refreshOnce 结束后把基线实例回传,供页面/测试观测请求量、
* 响应体大小与缓存内存占用。
*/
onBaseline?: (baseline: TaskPollingBaseline) => void
}
export interface TaskProgressLoopHandle<TDetail> {
@@ -95,6 +105,7 @@ export function useTaskProgressLoop<TDetail>(
const timers = createCategorizedTimers(`task-progress-loop:${options.scope}`)
const isTerminal = options.isTerminal ?? DEFAULT_TERMINAL
const intervalMs = options.getIntervalMs ?? getTaskPollIntervalMs
const baseline = options.onBaseline ? createTaskPollingBaseline() : null
const taskIds = ref<number[]>(readIdsFromStorage(options.storageKey))
const taskStatuses = ref<Record<number, string>>({})
@@ -141,6 +152,7 @@ export function useTaskProgressLoop<TDetail>(
const ids = taskIds.value.filter((id) => id > 0)
if (!ids.length) return
inFlight.value = true
baseline?.recordRequest()
try {
const result = await options.fetchProgress(ids)
const items = result?.items || []
@@ -175,6 +187,7 @@ export function useTaskProgressLoop<TDetail>(
options.onError?.(error)
} finally {
inFlight.value = false
if (baseline) options.onBaseline?.(baseline)
}
}