要构建一个自定义的异步调度器,核心并不在于依赖框架自带的定时器(如 `setTimeout` 或 `setInterval`),而是学会借力事件循环,手动控制任务的延迟执行、周期触发以及优先级调度。Genspark 本身并未提供现成的调度器,但这也带来了更大的灵活度——其异步模型(基于生成器/协程 + Promise 驱动)天然适合搭建轻量、可组合的调度逻辑。

从最基本的概念说起:Genspark 中的每个“spark”本质上都是一个可暂停、可恢复的生成器函数。当它 `yield` 出一个 Promise 后,运行时会接管,待该 Promise 定下结果后再继续往下执行。这意味着完全无需操心微任务队列的管理,而是需要善用 `yield` 和 Promise 的组合,将调度语义嵌入其中。
每次 `yield` 一个 Promise,相当于把控制权交回 Genspark 运行时,待 Promise 落定后再继续执行。可以将 `delay`、`repeat`、`throttle` 这类行为封装成返回 Promise 的工具函数,然后在 spark 中直接 `yield` 它们。调度器本身并不需要设计成全局唯一的单例——反而是按场景创建独立的调度实例更为可靠,测试和生命周期管理都更便利。
实现基础延迟与重复调度
从最简单可用的设计开始:支持 `delay(ms)` 和 `repeat(fn, interval)`。关键思路不是轮询,而是用 Promise 配合 `setTimeout` 搭建一套可中断的链式调度。
- **delay**:返回一个 Promise,在指定毫秒后 resolve,在 spark 中直接 `yield delay(1000)` 即可实现延时效果。
- **repeat**:返回一个可取消的 Promise(resolve 时顺带传出一个 cancel 函数),内部通过递归 `setTimeout` 驱动,从而避免累积误差。注意不要在 repeat 内直接 `yield` 自身——那会导致无限嵌套;正确的做法是 yield 每次调用后的 delay Promise。
加入优先级与任务队列
当多个 spark 共享同一个调度上下文时,任务优先级成为刚需。可以设计一个内存中的最小堆(或简单使用数组排序)来管理待执行的任务:
- 每个任务携带 `time`(绝对触发时间戳)、`priority`(数值越小优先级越高)、`fn`(执行函数)。
- 调度器的主循环只负责 peek 当前优先级最高的就绪任务(即 `time <= Date.now()`),执行并移除。
- 新增任务时插入堆并重新排序;支持通过标记 `isCanceled = true` 跳过执行。
- 不要在 spark 内部直接操作该队列——应暴露 `scheduler.schedule(fn, delay, priority)` 接口供 yield 调用。
与 Genspark 生命周期集成
确保调度器能随 spark 启动和销毁自动清理,这一点至关重要:
- 在 spark 初始化时创建调度器实例,并绑定到 spark context(例如 `this.scheduler`)。
- 所有通过 `scheduler.schedule()` 提交的任务自动关联当前 spark 的生命周期。
- 当 spark 被终止(如用户跳转、组件卸载),调用 `scheduler.clear()` 取消所有 pending 任务。
- 尽量避免使用全局的 `setInterval`——改用单个 `requestIdleCallback` 或递归 `setTimeout` 驱动队列,可有效降低 CPU 占用。