Files
crawler-plugin/frontend-vue/src/shared/task-progress-config.ts
T
huangzd1997 58e91589df task-86: 将隐藏页面轮询间隔、前台恢复和退避策略统一配置化
task-progress-config 新增统一配置 API:getTaskPollBackoffMs(attempt)
指数退避(base×2^attempt 封顶 max)、前台恢复开关与延迟
(getTaskForegroundRefreshEnabled/DelayMs)、configureTaskPolling/
resetTaskPollingConfig 运行时配置。校验失败抛错且零状态变更,
document 缺失时按可见间隔降级。useTaskProgressLoop 改用退避配置
替代硬编码 500ms 重试,前台恢复走可配置开关与延迟(含 in-flight 防
并发)。8 个测试覆盖默认、多配置、幂等、空、单字段、封顶、非法输入
与故障恢复。
2026-08-30 22:51:21 +08:00

93 lines
3.4 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 86 扩展)。
*
* 把隐藏页面轮询间隔、前台恢复与退避策略统一收敛为可编程配置:
* - getTaskPollIntervalMs / getTaskProgressCacheTtlMs:按 document.visibilityState
* 自适应切换可见/隐藏间隔与缓存 TTL(历史行为不变);
* - getTaskPollBackoffMs(attempt):指数退避(base × 2^attempt,封顶 max),
* 供轮询失败/in-flight 重试使用,重试次数不再硬编码;
* - getTaskForegroundRefreshEnabled / getTaskForegroundRefreshDelayMs:控制
* 页面切回前台时是否立即拉取一轮及其延迟;
* - configureTaskPolling / resetTaskPollingConfig:运行时配置与恢复默认。
*
* 纯 TS 模块:配置校验失败时抛错且保持原值(零状态变更),document 缺失
* (Node/SSR)时按可见间隔降级;所有 getter 在任意配置下都有确定返回值。
*/
export const TASK_POLL_VISIBLE_INTERVAL_MS = 10000;
export const TASK_POLL_HIDDEN_INTERVAL_MS = 60000;
export const TASK_PROGRESS_VISIBLE_CACHE_MILLIS = 5000;
export const TASK_PROGRESS_HIDDEN_CACHE_MILLIS = 30000;
export const TASK_POLL_BACKOFF_BASE_MS = 500;
export const TASK_POLL_BACKOFF_MAX_MS = 5000;
export interface TaskPollingConfig {
/** 退避基数(毫秒),必须为正数 */
backoffBaseMs?: number;
/** 退避上限(毫秒),必须为正数 */
backoffMaxMs?: number;
/** 页面切回前台时是否立即拉取一轮,默认 true */
foregroundRefreshEnabled?: boolean;
/** 前台恢复延迟(毫秒),默认 0 */
foregroundRefreshDelayMs?: number;
}
const DEFAULTS: Required<TaskPollingConfig> = {
backoffBaseMs: TASK_POLL_BACKOFF_BASE_MS,
backoffMaxMs: TASK_POLL_BACKOFF_MAX_MS,
foregroundRefreshEnabled: true,
foregroundRefreshDelayMs: 0,
};
let config: Required<TaskPollingConfig> = { ...DEFAULTS };
function isVisible() {
return typeof document === "undefined" || document.visibilityState === "visible";
}
export function getTaskPollIntervalMs() {
if (!isVisible()) {
return TASK_POLL_HIDDEN_INTERVAL_MS;
}
return TASK_POLL_VISIBLE_INTERVAL_MS;
}
export function getTaskProgressCacheTtlMs() {
if (typeof document !== "undefined" && document.hidden) {
return TASK_PROGRESS_HIDDEN_CACHE_MILLIS;
}
return TASK_PROGRESS_VISIBLE_CACHE_MILLIS;
}
/** 第 attempt 次退避延迟:base × 2^attempt,封顶 maxattempt 非法时按首次(0)处理 */
export function getTaskPollBackoffMs(attempt: number) {
const safe = Number.isFinite(attempt) && attempt > 0 ? Math.floor(attempt) : 0;
const doubled = config.backoffBaseMs * Math.pow(2, safe);
return doubled > config.backoffMaxMs ? config.backoffMaxMs : doubled;
}
export function getTaskForegroundRefreshEnabled() {
return config.foregroundRefreshEnabled;
}
export function getTaskForegroundRefreshDelayMs() {
return config.foregroundRefreshDelayMs;
}
export function configureTaskPolling(partial: TaskPollingConfig) {
const next = { ...config, ...partial };
if (!(next.backoffBaseMs > 0)) {
throw new Error("退避基数必须为正数: " + next.backoffBaseMs);
}
if (!(next.backoffMaxMs > 0)) {
throw new Error("退避上限必须为正数: " + next.backoffMaxMs);
}
if (!(next.foregroundRefreshDelayMs >= 0)) {
throw new Error("恢复延迟不能为负数: " + next.foregroundRefreshDelayMs);
}
config = next;
}
export function resetTaskPollingConfig() {
config = { ...DEFAULTS };
}