Bun 启动时 setImmediate 抢在已到期定时器前执行
Bun 1.4.4 的首次事件循环可能先执行 setImmediate,再处理已到期的短定时器。不要把两种调度方式当作初始化依赖,应显式等待前置步骤。
初始化先放进 setTimeout(fn, 1),随后用 setImmediate() 读取结果,Bun 中却先读到了未初始化的状态。即使入口同步代码已经执行了十几毫秒,定时器看起来早该到期,次序仍可能与预期不同。
Bun 1.4.4 有启动阶段先执行 immediate、再处理已到期 timer 的具体表现;1.4.2 与 Linux 上的 1.4.3 也出现过相同次序。它不能变成“Bun 的所有 immediate 总在 timer 前面”这条规则。
区分入口首次循环和回调内部
入口里安排两个回调,与在定时器、I/O 回调里重新安排两个回调,所处的循环阶段不同。把一段测试搬进另一个回调后次序改变,不能证明业务已经消除了竞争。
Bun 1.4.4 的事件循环实现包含先处理 immediate 任务、再推进 timer 的路径。Node 的主模块中 timeout 与 immediate 的先后也不适合作为通用同步契约;某个机器上稳定看到同一顺序,不足以让两个异步任务自动建立依赖。
不要在入口加入忙等,让定时器“足够过期”。阻塞循环会拖延其他工作,仍没有表达哪个初始化步骤必须先完成。
等待真正的前置工作
若后一步必须使用前一步的结果,让前一步返回可等待的完成信号。例如,原来初始化连接的方法应在连接可用后才兑现 Promise:
async function start() {
const connection = await initializeConnection();
await serveWith(connection);
}
start().catch((error) => {
console.error(error);
process.exitCode = 1;
});
initializeConnection 和 serveWith 在这里代表项目已有的方法,需要按实际契约实现。不能只把同步启动动作包进 Promise.resolve(),却在资源还没准备好时就兑现。使用回调接口时,也应从它的完成回调继续后续工作,而不是另外安排一个 immediate 猜测时机。
如果真正的需求就是延迟某段代码,而不依赖另一项任务,保留合适的调度 API 即可。没有必要把所有定时器都改成微任务;微任务优先级和饥饿风险不同,也不等于资源准备完成。
用状态约束验收,不固定打印顺序
回归应检查服务启动前连接确实可用、初始化失败时不会继续接受业务、停止后不会留下未清理的 timer。异步测试要等待完成信号,不能只读取一轮日志来猜任务是否结束。
需要兼容多个运行时时,保留各自的启动入口测试,并避免断言没有被接口契约保证的 timeout/immediate 顺序。把业务依赖改成明确的完成关系,才能避免让调度细节继续决定初始化是否成功。


