用 Bun.cron 定时 7 月 7 日,为啥? 看图1

bash 复制代码
// 7 月 7 日 0 点整,跑一次
Bun.cron("0 0 7 7 *", () => {
  console.log("7 月 7 日到了,Bun 1.4 rust 发布了");
});
bash 复制代码
bun run job.ts

完事儿。

进程不退出,7 月 7 日凌晨 0 点 0 分,handler 自动触发。

为啥是7月7日?

⭐bun1.4 也就是rust版本就要发布了, 真的很期待, 等到时候我会写一篇1.4介绍的文章。

根据官方x上的介绍, 已知变化有:

  • 底层换 Rust

  • React 编译快 19 倍

  • SourceMap 解码快 3 倍

  • hex 编码快 4.8 倍

  • base64url 快 41 倍

  • CJK 宽度计算快 56 倍

  • fetch 自动压缩请求体

  • 内存不足自动预警


痛点:写个定时任务有多折腾

老办法写一遍:

bash 复制代码
# 装包
npm i node-cron
bash 复制代码
import cron from "node-cron";
cron.schedule("0 0 7 7 *", () => {
  sendCoupon();
});

先别说 node-cron 五年没更新了。

就说跨平台这事儿。

Linux 要写 crontab。macOS 要写 launchd。Windows 要写 Task Scheduler。

三套配置

三个调试姿势

部署上线,crontab 没装,任务根本起不来

Bun.cron 就是来治这个的。

v1.3.11 内置,啥都不用装

进程内 Bun.cron()、系统级 Bun.cron(path, schedule, title) 一把梭。


一、进程内定时:3 行起步

Bun.cron(schedule, handler) 在当前进程里跑。

handler 可以是 async

下一个触发时间,等 handler 跑完再算

不会堆叠

bash 复制代码
// 每天 9 点整
Bun.cron("0 9 * * *", async () => {
  await syncOrders();
});

// 每周一到周五,下午 6 点
Bun.cron("0 18 * * MON-FRI", async () => {
  await generateDailyReport();
});

// 7 月 7 日 0 点 0 分,给老用户发券
Bun.cron("0 0 7 7 *", async () => {
  await sendCouponsToVIPs();
});

返回值是个 handle

bash 复制代码
const job = Bun.cron("0 0 7 7 *", () => {});

job.cron;     // "0 0 7 7 *"
job.stop();   // 取消
job.unref();  // 进程不再被它拖住
job.ref();    // 恢复

还能用 using 自动停:

bash 复制代码
using job = Bun.cron("0 0 7 7 *", () => {});
// 作用域结束自动 stop

二、cron 表达式速查

Bun.cron 用标准 5 字段:分 时 日 月 周

| 字段 | 取值 | | :-- | :-- | | 分 | 0-59 | | 时 | 0-23 | | 日 | 1-31 | | 月 | 1-12(也支持 JAN-DEC) | | 周 | 0-7(0 和 7 都是周日,支持 SUN-SAT) |

四个常用特殊字符:

| 符号 | 含义 | 例子 | | :-- | :-- | :-- | | * | 任意 | * * * * * 每分钟 | | , | 列举 | 1,15 * * * * 第 1、第 15 分 | | - | 区间 | 9-17 * * * * 9 到 17 分 | | / | 步长 | */15 * * * * 每 15 分钟 |

几个常用组合:

bash 复制代码
// 7 月 7 日 0 点 0 分
Bun.cron("0 0 7 7 *", handler);

// 7 月 7 日 12 点 0 分
Bun.cron("0 12 7 7 *", handler);

// 7 月 7 日 18 点 30 分
Bun.cron("30 18 7 7 *", handler);

// 7 月 7 日 18 点 30 分(命名法)
Bun.cron("30 18 7 JUL *", handler);

还有几个内置别名:

bash 复制代码
"@yearly"   // 0 0 1 1 *   1 月 1 日
"@monthly"  // 0 0 1 * *   每月 1 号
"@weekly"   // 0 0 * * 0   每周日
"@daily"    // 0 0 * * *   每天 0 点
"@hourly"   // 0 * * * *   每小时

MON-FRI 这种区间名字也支持。

JAN,JUN 多选也行。

完整解析都按 POSIX 标准来。


三、解析下次触发时间:Bun.cron.parse

有时候你不想立刻跑。

就想知道下次啥时候跑

Bun.cron.parse("...") 直接返回 Date 对象。

bash 复制代码
// 下一次 7 月 7 日 0 点 0 分
const next = Bun.cron.parse("0 0 7 7 *");
console.log(next);
// => 2026-07-07T00:00:00.000Z

// 下一次工作日 9 点半
const workday = Bun.cron.parse("30 9 * * MON-FRI");
console.log(workday);

// 每 15 分钟
const q = Bun.cron.parse("*/15 * * * *");
console.log(q); // 最近的整 15 分

返回 UTC 时间

没有夏令时问题

8 年内找不到匹配返回 null(比如 2 月 30 号,永远不会有)。

还能链式查接下来 N 次:

bash 复制代码
let cursor: Date | number = Date.now();
for (let i = 0; i < 3; i++) {
  cursor = Bun.cron.parse("0 * * * *", cursor)!;
  console.log(cursor.toLocaleString());
  // 输出接下来三个整点
}

写预约系统、倒计时页面、提醒功能的时候特别香


四、系统级 cron:进程死了也能跑

进程内 cron 跑得欢。

但进程一退出,就停了

服务器重启、Docker 容器干掉、PM2 reload 一下------任务没了

想要进程死了也跑

用系统级 Bun.cron(path, schedule, title)

bash 复制代码
// 注册一个系统级 cron
// 每 30 分钟跑一次 ./worker.ts
await Bun.cron("./worker.ts", "*/30 * * * *", "heartbeat");

Bun 内部自动调用操作系统的调度器:

| 平台 | 调度器 | | :-- | :-- | | Linux | crontab | | macOS | launchd plist | | Windows | Task Scheduler |

同一个 title 重复注册

覆盖 旧的,不会重复。

bash 复制代码
// 这次每 1 小时
await Bun.cron("./worker.ts", "0 * * * *", "heartbeat");

// 改成每 15 分钟
await Bun.cron("./worker.ts", "*/15 * * * *", "heartbeat");
// 旧任务被覆盖,不会跑两个

被调度的脚本必须 export default 一个对象

bash 复制代码
// worker.ts
export default {
  async scheduled(controller) {
    console.log("cron:", controller.cron);
    // "0 * * * *"
    console.log("时间:", new Date(controller.scheduledTime).toISOString());
    await doWork();
  },
};

API 跟 Cloudflare Workers 的 Cron Triggers 一模一样

写过 CF Workers 的兄弟零学习成本

删除也很简单:

bash 复制代码
await Bun.cron.remove("heartbeat");

Linux 上看一眼注册了啥

bash 复制代码
crontab -l

你会看到 Bun 自动加的标记:

bash 复制代码
# bun-cron: heartbeat
*/30 * * * * '/usr/local/bin/bun' run --cron-title=heartbeat --cron-period='*/30 * * * *' '/path/worker.ts'

macOS 看一眼:

bash 复制代码
launchctl list | grep bun.cron

Windows 看一眼:

bash 复制代码
schtasks /query | findstr "bun-cron-"

五、实战:7 月 7 日 0 点发奖

来个真实业务场景

7 月 7 日 0 点,给所有 VIP 用户发一张满 100 减 20 的券

只发一次7 月 8 日之后这个 cron 就没用了

方案 A:进程内 cron

写一个 long-running 服务:

bash 复制代码
// job.ts
import { SQL } from "bun";

// 内存里 1 个 cron
Bun.cron("0 0 7 7 *", async () => {
  const sql = new SQL("postgres://user:pass@localhost:5432/shop");

  // 用 Bun.sql 原生 prepared statement,防注入
  const vips = await sql`
    SELECT id FROM users WHERE level = 'vip'
  `;

  for (const user of vips) {
    await sql`
      INSERT INTO coupons (user_id, amount, expires_at)
      VALUES (${user.id}, 20, '2026-07-31')
    `;
  }

  console.log(`已发 ${vips.length} 张券`);
});
bash 复制代码
bun run job.ts

服务挂着。

7 月 7 日 0 点 0 分自动跑

7 月 8 日之后 ,这个 cron 永远不会触发(今年 7 月 7 日过完)。

进程也不需要重启

方案 B:系统级 cron

7 月 7 日 0 点跑一次

bash 复制代码
// register.ts
await Bun.cron("./send-coupons.ts", "0 0 7 7 *", "july7-coupon");
console.log("已注册到系统 cron");
bash 复制代码
// send-coupons.ts
import { SQL } from "bun";

export default {
  async scheduled() {
    const sql = new SQL("postgres://user:pass@localhost:5432/shop");
    const vips = await sql`SELECT id FROM users WHERE level = 'vip'`;

    for (const user of vips) {
      await sql`
        INSERT INTO coupons (user_id, amount, expires_at)
        VALUES (${user.id}, 20, '2026-07-31')
      `;
    }

    console.log(`已发 ${vips.length} 张券`);

    // 跑完就注销,省得明年 7 月 7 日又跑一次
    await Bun.cron.remove("july7-coupon");
  },
};
bash 复制代码
bun run register.ts

7 月 7 日 0 点,操作系统准时拉起 Bun。

跑完自动注销

明年 7 月 7 日不重复


六、进程内 vs 系统级,怎么选

| 维度 | 进程内 Bun.cron(schedule, handler) | 系统级 Bun.cron(path, schedule, title) | | :-- | :-- | :-- | | 进程退出 | 停了 | 不停 | | 共享状态 | 有 | 没(每次新进程) | | 跨平台限制 | 无 | Windows 有 48 trigger 上限 | | 返回值 | CronJob handle | Promise<void> | | 适用场景 | 长期服务、worker、API 后台任务 | 一次性任务、运维脚本、数据清理 |

长期挂着不死的服务 (API、Worker),用进程内

单次、长时间间隔、不能挂服务 (凌晨 3 点备份、跨年发奖),用系统级


七、几个容易踩的坑

坑 1:UTC 时间

进程内parse() 全部走 UTC

bash 复制代码
// 这不是早上 9 点,是 UTC 9 点
Bun.cron("0 9 * * *", handler);

国内 业务想跑北京时间早上 9 点:

bash 复制代码
// UTC 9 点 = 北京时间 17 点
// 北京时间 9 点 = UTC 1 点
Bun.cron("0 1 * * *", handler);

或者TZ=UTC 起服务,所有时间统一 UTC

系统级 cron 走系统本地时区(crontab、launchd、Task Scheduler 默认就这样)。

跨平台时两种方式可能差几小时

坑 2:Windows 48 trigger 上限

Windows Task Scheduler 一个任务最多 48 个触发器

*/5 * * * * 这种 *+步长的,Bun 会自动转成 Repetition1 个 trigger 搞定

*/7 * * * * 这种不能整除 60 的 ,Bun 会展开

bash 复制代码
// 9 个分 × 24 小时 = 216 trigger ❌ Windows 拒绝
await Bun.cron("./job.ts", "*/7 * * * *", "my-job");

// 改成 9 个分 × 5 小时 = 45 trigger ✅
await Bun.cron("./job.ts", "*/7 9-13 * * *", "my-job");

跨平台部署 的时候,老老实实用 60 的因数*/1 */2 */3 */4 */5 */6 */10 */12 */15 */20 */30

坑 3:Windows 容器不支持

Windows Docker 容器 (servercore、nanoserver)没有 Task Scheduler 服务

Bun.cron("./job.ts", ...) 在容器里直接报错

容器场景用进程内 cron

坑 4:错误处理

handler 抛错setTimeout 语义:

  • 同步 throwprocess.on("uncaughtException")

  • Promise rejectprocess.on("unhandledRejection")

没监听进程退出码 1

有监听任务继续跑下次到点再试。

bash 复制代码
process.on("unhandledRejection", (err) => {
  log.error("cron 任务挂了:", err);
});

Bun.cron("0 0 7 7 *", async () => {
  // 抛错不会让任务停
  await mightFail();
});

八、Bun 1.4 rust 版要来了

Bun.cron 现在是 Zig 实现的。

下个大版本,Bun 1.4核心引擎 Zig 换 Rust

PORTING.md.claude/workflowsAI 流水线迁移

5 月 14 日 那个 Rewrite Bun in Rust 的 PR 已经 merge。

Linux x64 glibc 99.8% 测试通过

二进制瘦了 3~8 MB

性能持平或更快

想尝鲜

bash 复制代码
bun upgrade --canary

进程内系统级 cron 在 rust 版里 API 完全不变

代码不用动

有数据状态

  • • v1.3.11 引入 Bun.cron

  • • v1.3.14 是 1.3 收官版

  • • 1.4 还在 canary

  • • 7 月 7 日左右出 stable

赶 7 月 7 日发奖

  • • 业务代码现在Bun.cron

  • • 上线先用 1.3.x stable

  • • 1.4 出了直接 bun upgrade

  • • 代码一行不用改


九、收个尾

Bun.cron 干的就这一件事:

让定时任务少折腾

  • • 进程内 3 行起步

  • • 系统级一个 await 注册

  • • 跨平台一套 API

  • • 表达式支持命名、范围、步长、OR 逻辑

  • • 跟 Cloudflare Workers 一样的 scheduled() handler

  • • 错误不打断后续调度

node-cron 五年没更新。

自己写 crontab 维护成本高。

Bun 直接把 cron 焊死在 runtime 里

7 月 7 日发个券,就这一行

bash 复制代码
Bun.cron("0 0 7 7 *", sendCoupons);

完事儿


相关推荐
之歆3 小时前
Vue商品详情与放大镜组件
前端·javascript·vue.js
丹宇码农8 小时前
把 HLS 字幕玩出花:zwPlayer 如何让 M3U8 视频支持全文搜索、翻译与码率自适应
前端·javascript·音视频·hls·视频播放器
GuWenyue9 小时前
提示词彻底过时?一套上下文工程方案,3步让LLM落地生产,代码直接复用
前端·javascript·人工智能
用户938515635079 小时前
深入理解 JavaScript 中的 this 与数据存储的奥秘
前端·javascript
Tian_Hang9 小时前
eclipse ditto 学习笔记
运维·服务器·开发语言·javascript·3d
竹林81812 小时前
用 Pinata + IPFS 存 NFT 元数据踩了三天坑,我总结了这份完整的前端实现方案
javascript
林希_Rachel_傻希希12 小时前
web性能优化之延迟加载图片和<inframe>
前端·javascript·面试
小米渣的逆袭13 小时前
Chrome Extension Script World(ISOLATED / MAIN)原理与适用场景
前端·javascript·chrome
Esaka_Forever14 小时前
Python 与 JS (V8) 垃圾回收核心区别 + 底层根源分析
开发语言·javascript·jvm