Handler 定时任务工具

GB.Handler 是一个零依赖的轻量级定时任务工具。它将浏览器原生 Timer API 封装为链式接口,可用于延迟执行、限次循环和持续轮询,并提供任务创建前后的生命周期回调。

快速开始

一个任务通常由“创建实例、配置参数、启动任务”三步组成。下面的任务会等待 1 秒,然后执行一次。

GB.Handler()
  .delay(1000)
  .start(() => {
    console.log('1 秒后执行');
  });

需要重复执行时,再通过 cycle() 指定次数;设置为 -1 时会持续运行,务必保存 start() 的返回值,以便后续停止。

安装与引入

浏览器直接引入

在业务脚本之前引入,之后即可使用全局方法 GB.Handler()。

<script src="https://cloud.m775.net/js/handler.js"></script>

npm 项目

npm i @gentbo/handler

import '@gentbo/handler';

理解执行流程

1. GB.Handler()创建一个独立的 Handler 实例。
2. 配置使用 cycle、delay、before 和 after 描述任务行为。
3. start(task)先执行 before,再创建原生定时器,随后执行 after。
4. 等待 delay到达设定时间后执行 task;循环任务会按配置继续调度。

before() 和 after() 围绕的是“定时器创建”过程,而不是每一次 task 执行。特别是 after() 不会等待循环任务全部完成。

控制执行次数

.cycle(cycle) 设置任务的理论执行次数。配置方法都会返回当前实例,可继续链式调用。

.cycle(-1)无限循环,直到通过返回的 Timer ID 主动停止。
.cycle(0 / 1)执行一次,适合普通延迟任务。
.cycle(n)当 n 大于 1 时,按指定次数循环执行。
GB.Handler()
  .cycle(3)
  .delay(1000)
  .start(() => {
    console.log('执行');
  });

// 第 1、2、3 秒各执行一次,随后结束

控制执行间隔

.delay(delay) 同时用于设置首次等待时间和循环间隔,参数单位为毫秒。内部最小值为 4ms;传入更小的值时,实际按 4ms 调度。

GB.Handler()
  .cycle(-1)
  .delay(1000)
  .start(() => {
    console.log('每秒执行一次');
  });

前置与后置处理

.before(callback)注册前置回调,在创建定时器之前执行。
.after(callback)注册后置回调,在定时器创建完成后执行;它不会等待循环任务全部结束。
GB.Handler()
  .delay(1000)
  .before(() => {
    console.log('准备创建任务');
  })
  .after(() => {
    console.log('任务已创建');
  })
  .start(() => {
    console.log('执行任务');
  });

启动、返回值与停止

.start(task) 接收任务函数并立即完成定时器创建,返回对应的原生 Timer ID,而不是任务函数的返回值。请在组件卸载、页面切换或业务结束时清理仍在运行的任务。

const pollingTimer = GB.Handler()
  .cycle(-1)
  .delay(30000)
  .start(() => {
    refreshData();
  });

// 停止无限循环任务
clearInterval(pollingTimer);

const delayedTimer = GB.Handler()
  .delay(5000)
  .start(() => console.log('5 秒后执行'));

// 取消尚未执行的延迟任务
clearTimeout(delayedTimer);

API 速查

GB.Handler()创建并返回新的 Handler 定时任务实例。
.cycle(number)设置执行次数;-1 表示无限循环,0 或 1 表示执行一次。
.delay(number)设置延迟或循环间隔,单位为毫秒,最小值为 4。
.before(function)设置创建定时器前执行的回调。
.after(function)设置创建定时器后执行的回调。
.start(task: Function)启动任务并返回原生 Timer ID;task 内的返回值不会作为 start 的返回值。

实用示例

倒计时

let count = 5;

GB.Handler()
  .cycle(5)
  .delay(1000)
  .start(() => console.log(count--));

定时刷新数据

const timer = GB.Handler()
  .cycle(-1)
  .delay(30000)
  .start(() => {
    fetch('/api/data')
      .then(response => response.json())
      .then(data => console.log(data));
  });

自动保存

const autoSaveTimer = GB.Handler()
  .cycle(-1)
  .delay(60000)
  .start(() => {
    saveDraft();
  });

window.addEventListener('beforeunload', () => {
  clearInterval(autoSaveTimer);
});

错误处理与计时限制

Handler 不会自动捕获任务中的异常。任务可能失败时,请在回调内部使用 try...catch,避免未处理异常影响页面逻辑。

GB.Handler().start(() => {
  try {
    runTask();
  } catch (error) {
    console.error(error);
  }
});

浏览器性能、系统资源、页面是否位于后台以及 Timer 节流策略都会影响实际间隔。因此 cycle() 表示理论执行次数,delay() 也不保证毫秒级精确。页面进入后台后,实际间隔通常会大于配置值。

如果循环任务包含耗时的异步请求,下一轮可能在上一轮结束前开始。需要严格串行时,应在请求完成后再创建下一次延迟任务,而不是使用无限循环。

适用范围与兼容性

适合页面轮询、数据刷新、倒计时、状态检测、自动保存和普通延迟任务。
不适合高精度计时、音视频同步和游戏引擎核心循环;逐帧动画优先使用 requestAnimationFrame。
运行环境支持 ES2020、Class Private Field、setTimeout 与 setInterval 的现代浏览器。
目标浏览器Chrome、Edge、Firefox、Safari,以及基于 Chromium 的常见内置浏览器。