Skip to main content
Bun 内置了对 cron 的支持——解析表达式,在进程内部按计划运行回调,或注册能够跨重启存活的操作系统级任务。

快速开始

在当前进程中按计划运行回调:
解析 cron 表达式以查找下一个匹配时间:
注册一个操作系统级 cron 任务,在指定计划下运行脚本:

Bun.cron.parse()

解析 cron 表达式,并返回系统本地时区中下一个匹配的 Date

参数

返回值

Date | null — 下一个匹配的时间;如果在 8 年内不存在匹配项,则返回 null(例如 2 月 30 日)。

链式调用

重复调用 parse() 以获取即将到来的时间序列:

Cron 表达式语法

标准 5 字段格式:minute hour day-of-month month day-of-week

特殊字符

命名值

月份和星期字段接受不区分大小写的名称:
在星期字段中,07 都表示星期日。

预定义别名

时区

计划任务按照系统的本地时区进行解释——其方式与 crontab、launchd 和 Windows 任务计划程序读取计划的方式相同。操作系统级形式和进程内回调形式会在相同的墙上时钟时间触发。 如需覆盖默认时区,请将 IANA 时区名称作为 { tz } 传递给 Bun.cron.parse() 或进程内的 Bun.cron(schedule, handler, options)
夏令时转换:
  • 春季调快 — 如果计划任务落在缺失的小时内,则会在当天触发,并因时间间隔而顺延(例如,30 2 * * * 会在春季调快当天的 3:30 运行)。对于缺失小时内的多分钟模式(*/15 2 * * *),只有第一个匹配项会触发。
  • 秋季调慢 — 在重复小时内的固定时间计划(30 1 * * *)只会触发一次,即在第一次出现时触发。分钟或小时字段为 * 的计划(0 * * * ** * * * *)会经过两次出现的时间——每个实际时间分钟触发一次,与 Linux 上的 crontab 行为一致。

月份中的日期与星期交互

同时指定了 day-of-month 和 day-of-week(两者都不是 *)时,只要任一条件为真,表达式就会匹配。这遵循 POSIX cron 标准。
当只指定了一个(另一个是 *)时,仅使用该字段进行匹配。

Bun.cron(schedule, handler) — 进程内

在当前进程内以 cron 计划运行回调。
进程内调度是长时间运行的服务器和工作进程的轻量级选项——无需系统 cron 守护进程,在所有平台上的行为都相同,并且会在多次调用之间共享状态(数据库连接池、缓存、模块级变量)。

参数

同步返回一个 CronJob。如果表达式无效、时区名称未知,或者表达式没有未来的发生时间(例如 "0 0 30 2 *",即 2 月 30 日),则抛出 TypeError

不重叠保证

只有在 handler(包括任何返回的 Promise)结束之后,才会计算下一次触发时间。如果你的 handler 需要 90 秒,而计划是 * * * * *,那么第二次触发将是 handler 完成后的第一个整分钟边界,而不是第一次触发后 60 秒。调用之间不会堆叠。

错误处理

错误行为与 setTimeout 保持一致:
  • 同步 throw 会触发 process.on("uncaughtException")
  • 被拒绝的返回 Promise 会触发 process.on("unhandledRejection")
没有监听器时,进程会以代码 1 退出。设置了监听器时,任务会继续运行——不会在第一次失败后停止。

bun --hot

bun --hot 下,所有进程内 cron 任务会在模块图重新求值之前立刻停止。之后你源代码中的每次 Bun.cron() 调用都会重新注册。修改计划、修改 handler 或者直接删除那一行,都将在保存时生效,而不会泄漏定时器。

CronJob 句柄

CronJobDisposable —— using job = Bun.cron(...) 会在作用域退出时自动停止。stop()ref()unref() 都会返回该 job 以便链式调用。

假定时器

进程内 cron 支持 jest.useFakeTimers()setSystemTime()advanceTimersByTime()runAllTimers() 控制其触发时间,因此你可以无需等待真实时钟即可测试计划回调。

Bun.cron(path, schedule, title) — 操作系统级别

注册一个操作系统级别的 cron 任务,按计划运行 JavaScript/TypeScript 模块。

参数

使用相同的 title 重新注册会就地覆盖现有任务——旧的计划会被替换,而不是重复添加。

scheduled() 处理程序

注册的脚本必须导出一个带有 scheduled() 方法的默认对象,遵循 Cloudflare Workers Cron Triggers API
worker.ts
处理程序可以是 async。Bun 会等待返回的 promise 完成后再退出。

各平台的工作原理

Linux

Bun 使用 crontab 注册任务。每个任务作为一行存储在您用户的 crontab 中,上方带有 # bun-cron: <title> 标记注释。 crontab 条目如下所示:
当 cron 守护进程触发任务时,Bun 会导入您的模块并调用 scheduled() 处理程序。 查看已注册的任务:
日志: 在 Linux 上,cron 输出进入系统日志。使用以下命令查看:
要将 stdout/stderr 捕获到文件,可直接在 crontab 条目中重定向输出,或在 scheduled() 处理程序内添加日志记录。 无需代码手动卸载:

macOS

Bun 使用 launchd 注册任务。每个任务作为 plist 文件安装在:
plist 使用 StartCalendarInterval 定义调度计划。支持包含范围、列表或步长的复杂模式 — Bun 会将它们扩展为笛卡尔积形式的多个 StartCalendarInterval 字典。 查看已注册的任务:
日志: stdout 和 stderr 写入到:
例如,一个标题为 weekly-report 的任务:
无需代码手动卸载:

Windows

Bun 使用基于 XML 任务定义的 Windows Task Scheduler,使用 CalendarTrigger 元素和 Repetition 模式。 大多数 cron 表达式都得到完全支持,包括 @daily@weekly@monthly@yearly、范围 (1-5)、列表 (1,15)、命名日/月和日期模式。

用户上下文

Bun 使用 S4U(用户服务)登录类型注册任务,即使注册用户未登录,任务也会以该用户身份运行 — 与 Linux crontab 的行为一致。不会存储密码。 TCP/IP 网络(fetch()、HTTP、WebSocket、数据库连接)正常工作。唯一的限制是 S4U 任务无法访问 Windows 身份验证的网络资源(SMB 文件共享、映射驱动器、Kerberos/NTLM 服务)。 在无头服务器和 CI 环境中,如果无法解析当前用户的安全标识符(SID) — 例如由 NSSM 或类似工具创建的服务帐户 — Bun.cron() 将失败,并显示解释该问题的错误信息。要解决此问题,请使用普通用户帐户运行 Bun,或使用 schtasks /create /xml <file> /tn <name> /ru SYSTEM /f 手动创建计划任务。

触发器限制

Windows Task Scheduler 对每个任务的触发器数量限制为 48 个CalendarTrigger 元素的 maxOccurs="48")。 某些在 Linux 和 macOS 上有效的 cron 表达式在 Windows 上会超出此限制。当模式超出限制时,Bun.cron() 将返回错误消息拒绝该表达式。
在所有平台上都有效的表达式: 在 Windows 上失败的表达式(但在 Linux 和 macOS 上有效): 关键因素是表达式是否可以使用 Repetition 间隔(单一触发器),或者必须展开为独立的 CalendarTrigger 元素。能整除 60的分钟步长(*/1*/2*/3*/4*/5*/6*/10*/12*/15*/20*/30)使用 Repetition,无论其他字段如何都能工作。不能整除 60 的步长(*/7*/8*/9*/11*/13 等)必须展开,而在 24 小时全天运行的情况下,数量很快会超过 48。 要解决此问题,请简化表达式或限制小时范围:

Windows 容器

Bun.cron() 在 Windows Docker 容器中不受支持。Task Scheduler 服务不在 servercorenanoserver 镜像中运行。对于容器化工作负载,请使用进程内调度器。
查看已注册的任务:
无需代码手动卸载:
或者打开任务计划程序(taskschd.msc),找到名为 bun-cron-<title> 的任务,右键单击并删除它。

Bun.cron.remove()

通过其标题移除先前注册的定时任务。适用于所有平台。
这会撤销 Bun.cron() 的操作: 移除一个不存在的任务会无错误地完成。