/** * 任务轮询配置(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 = { backoffBaseMs: TASK_POLL_BACKOFF_BASE_MS, backoffMaxMs: TASK_POLL_BACKOFF_MAX_MS, foregroundRefreshEnabled: true, foregroundRefreshDelayMs: 0, }; let config: Required = { ...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,封顶 max;attempt 非法时按首次(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 }; }