9e224929fb
getTaskPollBackoffMs 签名改为 attempt 可选(省略视为 0,向后兼容), 序列 500×factor^attempt、封顶 maxMs 行为由任务 29 的 factor 配置驱动。 新增 8 个测试:attempt0/1/2 值、序列单调、封顶、省略参数默认、 负 attempt 当 0、自定义 factor。
118 lines
4.3 KiB
TypeScript
118 lines
4.3 KiB
TypeScript
/**
|
||
* 任务轮询配置(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 = 10000;
|
||
export const TASK_POLL_BACKOFF_FACTOR = 2;
|
||
export const TASK_PROGRESS_TIMEOUT_MS = 10000;
|
||
|
||
export interface TaskPollingConfig {
|
||
/** 退避基数(毫秒),必须为正数 */
|
||
backoffBaseMs?: number;
|
||
/** 退避上限(毫秒),必须为正数 */
|
||
backoffMaxMs?: number;
|
||
/** 退避指数因子,<1 时钳制为 1 */
|
||
backoffFactor?: number;
|
||
/** 页面切回前台时是否立即拉取一轮,默认 true */
|
||
foregroundRefreshEnabled?: boolean;
|
||
/** 前台恢复延迟(毫秒),默认 0 */
|
||
foregroundRefreshDelayMs?: number;
|
||
/** 进度类请求超时(毫秒),默认 10s,必须为正数 */
|
||
progressTimeoutMs?: number;
|
||
}
|
||
|
||
const DEFAULTS: Required<TaskPollingConfig> = {
|
||
backoffBaseMs: TASK_POLL_BACKOFF_BASE_MS,
|
||
backoffMaxMs: TASK_POLL_BACKOFF_MAX_MS,
|
||
backoffFactor: TASK_POLL_BACKOFF_FACTOR,
|
||
foregroundRefreshEnabled: true,
|
||
foregroundRefreshDelayMs: 0,
|
||
progressTimeoutMs: TASK_PROGRESS_TIMEOUT_MS,
|
||
};
|
||
|
||
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 × factor^attempt,封顶 max;attempt 省略/非法时按首次(0)处理 */
|
||
export function getTaskPollBackoffMs(attempt?: number) {
|
||
const safe = Number.isFinite(attempt) && (attempt as number) > 0 ? Math.floor(attempt as number) : 0;
|
||
const factor = getTaskPollBackoffFactor();
|
||
const scaled = config.backoffBaseMs * Math.pow(factor, safe);
|
||
return scaled > config.backoffMaxMs ? config.backoffMaxMs : scaled;
|
||
}
|
||
|
||
export function getTaskPollBackoffMaxMs() {
|
||
return config.backoffMaxMs;
|
||
}
|
||
|
||
/** 退避指数因子,<1 时钳制为 1 */
|
||
export function getTaskPollBackoffFactor() {
|
||
return config.backoffFactor < 1 ? 1 : config.backoffFactor;
|
||
}
|
||
|
||
export function getTaskForegroundRefreshEnabled() {
|
||
return config.foregroundRefreshEnabled;
|
||
}
|
||
|
||
export function getTaskForegroundRefreshDelayMs() {
|
||
return config.foregroundRefreshDelayMs;
|
||
}
|
||
|
||
export function getTaskProgressTimeoutMs() {
|
||
return config.progressTimeoutMs;
|
||
}
|
||
|
||
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);
|
||
}
|
||
if (!(next.progressTimeoutMs > 0)) {
|
||
throw new Error("progressTimeoutMs 必须为正数: " + next.progressTimeoutMs);
|
||
}
|
||
config = next;
|
||
}
|
||
|
||
export function resetTaskPollingConfig() {
|
||
config = { ...DEFAULTS };
|
||
}
|