Skip to main content

Documentation Index

Fetch the complete documentation index at: https://bun.zhcndoc.com/llms.txt

Use this file to discover all available pages before exploring further.

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

快速开始

在当前进程中按计划运行回调:
Bun.cron("0 * * * *", async () => {
  await cleanupTempFiles();
});
解析 cron 表达式以查找下一个匹配时间:
// 下一个工作日上午 9:30 UTC
const next = Bun.cron.parse("30 9 * * MON-FRI");
注册一个操作系统级 cron 任务,在指定计划下运行脚本:
await Bun.cron("./worker.ts", "30 2 * * MON", "weekly-report");

Bun.cron.parse()

解析一个 cron 表达式,并返回 UTC 中下一个匹配的 Date
const next = Bun.cron.parse("*/15 * * * *");
console.log(next); // => 下一个一刻钟边界

参数

参数类型描述
expressionstring5 字段的 cron 表达式或预定义别名
relativeDateDate | number搜索的起始点(默认为 Date.now()

返回值

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

链式调用

重复调用 parse() 以获取即将到来的时间序列:
let cursor: Date | number = Date.now();
for (let i = 0; i < 3; i++) {
  cursor = Bun.cron.parse("0 * * * *", cursor)!;
  console.log(cursor.toLocaleString()); // next three top-of-hour boundaries
}

Cron 表达式语法

标准 5 字段格式:minute hour day-of-month month day-of-week
字段取值特殊字符
Minute059* , - /
Hour023* , - /
Day of month131* , - /
Month112JANDEC* , - /
Day of week07SUNSAT* , - /

特殊字符

字符说明示例
*所有值* * * * * — 每分钟
,列表1,15 * * * * — 第 1 和第 15 分钟
-范围9-17 * * * * — 第 9 到 17 分钟
/步长*/15 * * * * — 每 15 分钟

命名值

月份和星期字段接受不区分大小写的名称:
// 3 字母缩写
Bun.cron.parse("0 9 * * MON-FRI"); // 工作日
Bun.cron.parse("0 0 1 JAN,JUN *"); // 一月和六月

// 完整名称
Bun.cron.parse("0 9 * * Monday-Friday");
Bun.cron.parse("0 0 1 January *");
在星期字段中,07 都表示星期日。

预定义别名

别名等效表达式说明
@yearly / @annually0 0 1 1 *每年一次(1 月 1 日)
@monthly0 0 1 * *每月一次(每月 1 日)
@weekly0 0 * * 0每周一次(周日)
@daily / @midnight0 0 * * *每天一次(午夜)
@hourly0 * * * *每小时一次
const next = Bun.cron.parse("@daily");
console.log(next); // => next UTC midnight

时区

Bun.cron.parse() 和进程内 Bun.cron(schedule, handler) 会使用 UTC 来解释计划。无需处理 DST(夏令时)——0 9 * * * 永远表示 9:00 UTC。 操作系统级 Bun.cron(path, schedule, title) 使用系统的本地时区,因为这就是 crontab、launchd 和 Windows 任务计划程序的工作方式。要让两种形式保持一致,请用 TZ=UTC 运行该进程。

月份中的日期与星期交互

同时指定了 day-of-month 和 day-of-week(两者都不是 *)时,只要任一条件为真,表达式就会匹配。这遵循 POSIX cron 标准。
// 在每月 15 日或每周五触发
Bun.cron.parse("0 0 15 * FRI");
当只指定了一个(另一个是 *)时,仅使用该字段进行匹配。

Bun.cron(schedule, handler) — in-process

在当前进程内以 cron 计划运行回调。
const job = Bun.cron("*/5 * * * *", async () => {
  await syncToDatabase();
});
这是适用于长期运行的服务器和工作线程的轻量选项——不需要系统 cron 守护进程,在每个平台上工作方式一致,并且在多次触发之间共享状态(数据库连接池、缓存、模块级变量)。
In-processOS-level
Survives process exit/rebootNoYes
Shared state between runsYesNo(每次都是全新进程)
Platform requirementsNonecrontab / launchd / Task Scheduler
Windows expression limitsNone48-trigger cap
Return typeCronJobPromise<void>

参数

参数类型描述
schedulestring一个 cron 表达式 或昵称(例如 "@hourly")。
handler(this: CronJob) => unknown每次触发都会调用。可以返回 Promise —— 在它完成之前不会安排下一次触发。在 function 回调内部,thisCronJob(因此 this.stop() 可以生效)。
同步返回一个 CronJob。如果表达式无效或在未来没有任何发生(例如 "0 0 30 2 *"——2 月 30 日),会抛出 TypeError

不重叠保证

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

错误处理

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

Bun.cron("* * * * *", async () => {
  await mightThrow(); // logged and retried next minute
});

bun --hot

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

CronJob 句柄

using job = Bun.cron("0 * * * *", () => {});

job.cron; // => "0 * * * *"
job.stop(); // cancel — the handler will not fire again
job.unref(); // allow the process to exit even while scheduled
job.ref(); // keep the process alive (default)
CronJobDisposable —— using job = Bun.cron(...) 会在作用域退出时自动停止。stop()ref()unref() 都会返回该 job 以便链式调用。

假定时器

进程内 cron 以真实的墙钟时间为基准。jest.useFakeTimers()setSystemTime()advanceTimersByTime()runAllTimers() 不会影响它何时触发。

Bun.cron(path, schedule, title) — OS-level

注册一个操作系统级别的 cron 任务,按计划运行 JavaScript/TypeScript 模块。
await Bun.cron("./worker.ts", "30 2 * * MON", "weekly-report");

参数

参数类型描述
pathstring脚本路径(相对于调用者解析)
schedulestringCron 表达式或别名
titlestring唯一任务标识符(字母数字、连字符、下划线)
使用相同的 title 重新注册会就地覆盖现有任务——旧的计划会被替换,而不是重复添加。
await Bun.cron("./worker.ts", "0 * * * *", "my-job"); // 每小时
await Bun.cron("./worker.ts", "*/15 * * * *", "my-job"); // 替换为:每 15 分钟

scheduled() 处理程序

注册的脚本必须导出一个带有 scheduled() 方法的默认对象,遵循 Cloudflare Workers Cron Triggers API
worker.ts
export default {
  scheduled(controller: Bun.CronController) {
    console.log(controller.cron); // "30 2 * * 1"
    console.log(controller.type); // "scheduled"
    console.log(controller.scheduledTime); // 1737340201847(调用时的 Date.now())
  },
};
处理程序可以是 async。Bun 会等待返回的 promise 完成后再退出。

各平台的工作原理

Linux

Bun 使用 crontab 注册任务。每个任务作为一行存储在您用户的 crontab 中,上方带有 # bun-cron: <title> 标记注释。 crontab 条目如下所示:
<schedule> '<bun-path>' run --cron-title=<title> --cron-period='<schedule>' '<script-path>'
当 cron 守护进程触发任务时,Bun 会导入您的模块并调用 scheduled() 处理程序。 查看已注册的任务:
crontab -l
日志: 在 Linux 上,cron 输出进入系统日志。使用以下命令查看:
# 基于 systemd 的系统(Ubuntu、Fedora、Arch 等)
journalctl -u cron       # 或在某些发行版上为 crond
journalctl -u cron --since "1 hour ago"

# 基于 syslog 的系统(旧系统)
grep CRON /var/log/syslog
要将 stdout/stderr 捕获到文件,可直接在 crontab 条目中重定向输出,或在 scheduled() 处理程序内添加日志记录。 无需代码手动卸载:
# 编辑您的 crontab 并删除 "# bun-cron: <title>" 注释
# 及其下方的命令行
crontab -e

# 或通过过滤一次性删除所有 bun cron 任务:
crontab -l | grep -v "# bun-cron:" | grep -v "\-\-cron-title=" | crontab -

macOS

Bun 使用 launchd 注册任务。每个任务作为 plist 文件安装在:
~/Library/LaunchAgents/bun.cron.<title>.plist
plist 使用 StartCalendarInterval 定义计划。支持包含范围、列表或步长的复杂模式——Bun 通过笛卡尔积将它们展开为多个 StartCalendarInterval 字典。 查看已注册的任务:
launchctl list | grep bun.cron
日志: stdout 和 stderr 写入到:
/tmp/bun.cron.<title>.stdout.log
/tmp/bun.cron.<title>.stderr.log
例如,一个标题为 weekly-report 的任务:
cat /tmp/bun.cron.weekly-report.stdout.log
tail -f /tmp/bun.cron.weekly-report.stderr.log
无需代码手动卸载:
# 从 launchd 卸载任务
launchctl bootout gui/$(id -u)/bun.cron.<title>

# 删除 plist 文件
rm ~/Library/LaunchAgents/bun.cron.<title>.plist

# 示例:标题为 "weekly-report" 的任务:
launchctl bootout gui/$(id -u)/bun.cron.weekly-report
rm ~/Library/LaunchAgents/bun.cron.weekly-report.plist

Windows

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

用户上下文

任务使用 S4U (Service-for-User) 登录类型注册,即使用户未登录也会以注册用户的身份运行任务——与 Linux crontab 行为一致。不存储密码。 TCP/IP 网络(fetch()、HTTP、WebSocket、数据库连接)正常工作。唯一的限制是 S4U 任务无法访问 Windows 认证的网络资源(SMB 文件共享、映射驱动器、Kerberos/NTLM 服务)。 在无法解析当前用户 Security Identifier (SID) 的无头服务器和 CI 环境中——例如由 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() 将返回错误消息拒绝该表达式。
在所有平台上都有效的表达式:
模式触发器策略数量
*/5 * * * *使用 Repetition (PT5M) 的单一触发器1
*/15 * * * *使用 Repetition (PT15M) 的单一触发器1
0 9 * * MON-FRI每个工作日一个 CalendarTrigger5
0,30 9-17 * * *2 分钟 × 9 小时18
@daily, @weekly, @monthly, @yearly单一触发器1
在 Windows 上失败的表达式(但在 Linux 和 macOS 上有效):
模式原因触发器数量
*/7 * * * *9 个分钟值 × 24 小时216
*/8 * * * *8 个分钟值 × 24 小时192
*/9 * * * *7 个分钟值 × 24 小时168
*/11 * * * *6 个分钟值 × 24 小时144
*/13 * * * *5 个分钟值 × 24 小时120
*/15 * * 6 *月份限制阻止了 Repetition:4 × 2496
0,30 * 15 * FRIOR 分割使触发器翻倍:2 × 24 × 296
关键因素是表达式是否可以使用 Repetition 间隔(单一触发器),或者必须展开为独立的 CalendarTrigger 元素。能整除 60的分钟步长(*/1*/2*/3*/4*/5*/6*/10*/12*/15*/20*/30)使用 Repetition,无论其他字段如何都能工作。不能整除 60 的步长(*/7*/8*/9*/11*/13 等)必须展开,而在 24 小时全天运行的情况下,数量很快会超过 48。 要解决此问题,请简化表达式或限制小时范围:
// ❌ 在 Windows 上失败:*/7 配合全天 = 216 个触发器
await Bun.cron("./job.ts", "*/7 * * * *", "my-job");

// ✅ 有效:限制为特定小时(9 个值 × 5 小时 = 45 个触发器)
await Bun.cron("./job.ts", "*/7 9-13 * * *", "my-job");

// ✅ 有效:改用 60 的约数(Repetition,1 个触发器)
await Bun.cron("./job.ts", "*/5 * * * *", "my-job");

Windows 容器

Bun.cron() 在 Windows Docker 容器中不受支持。Task Scheduler 服务不在 servercorenanoserver 镜像中运行。对于容器化工作负载,请使用进程内调度器。
查看已注册的任务:
schtasks /query /tn "bun-cron-<title>"

# 列出所有 bun cron 任务
schtasks /query | findstr "bun-cron-"
无需代码手动卸载:
schtasks /delete /tn "bun-cron-<title>" /f

# 示例:
schtasks /delete /tn "bun-cron-weekly-report" /f
或者打开任务计划程序(taskschd.msc),找到名为 bun-cron-<title> 的任务,右键单击并删除它。

Bun.cron.remove()

通过其标题移除先前注册的定时任务。适用于所有平台。
await Bun.cron.remove("weekly-report");
这会撤销 Bun.cron() 的操作:
平台remove() 的操作
Linux编辑 crontab 以移除该条目及其标记注释
macOS运行 launchctl bootout 并删除 plist 文件
Windows运行 schtasks /delete 以移除计划任务
移除一个不存在的任务会无错误地完成。