写过八年 JavaScript,每次资源清理都要靠 try/finally——今天 using 关键字把这件事彻底变了

写过文件读取,你一定见过这套:

let handle;
try {
  handle = await openFile('data.txt');
  // 读文件
} finally {
  await handle?.close(); // 清理
}

或者更保守一点,连初始化的那行也包进 try:

let handle;
try {
  handle = await openFile('data.txt');
  const data = await handle.read();
  // …
} finally {
  await handle?.close();
}

每次写这种代码我都要提醒自己:别漏了 finally,别漏了 await close,别在 finally 里再抛异常——三层考虑全靠习惯约束,没有任何语言层面的保障。

2026 年 8 月 13 日,这套写法正式过时了。

Safari 把最后一块拼图补上了

苹果的 WebKit 团队在 Safari Technology Preview 250 里完整支持了 TC39 的 Explicit Resource Management 提案。这个提案在 2025 年推进到 Stage 4,已经是 ECMAScript 2026 的正式组成部分。V8(Chrome / Node.js)和 SpiderMonkey(Firefox)早就实现了它,唯独 Safari 一直缺着。

STP 250 出来的那一刻,三大引擎第一次在 using 关键字上会师。这意味着它不再是 TypeScript 的转译目标,而是真正的原生运行时能力——不用 polyfill,不用编译,直接在浏览器里跑。

用 using 改写之后

// 之前:try/finally,三件事全靠人
let handle;
try {
  handle = await openFile('data.txt');
} finally {
  await handle?.close();
}

// 之后:using,退出作用域自动调用 [Symbol.dispose]()
{
  using handle = await openFile('data.txt');
  // 读文件
} // ← 作用域结束,浏览器自动调用 handle[Symbol.dispose]()

关键点在于:只要对象实现了 [Symbol.dispose](),离开 using 声明所在的作用域时,引擎就会自动调用它。不管是正常 return、抛异常、还是代码走到作用域底部,清理逻辑都会触发。

多资源按 LIFO 顺序自动清理,最后打开的先关:

{
  using file = await openFile('data.txt');
  using conn = await db.connect();

  // …业务逻辑

} // 先关 conn,再关 file

这和你手动写 finally 的顺序一致,不用再数栈的进出顺序。

抛异常的时候怎么办

之前 finally 里如果清理代码本身也抛异常,原来的错误就被吃掉了。新的 SuppressedError 把两个错都包在一起:

{
  using handle = await getHandle();
  throw new Error('业务异常');
} // handle.close() 抛错了 → 变成 SuppressedError,包含原始错误和清理错误

不是无声消失,也不是只留清理错——两个都保留。

异步资源用 await using

{
  await using conn = await db.connect();
  // …
} // 自动调用 conn[Symbol.asyncDispose]()

await using 配合 [Symbol.asyncDispose](),异步清理同样有保障。

谁在用这个

V8 团队在官方文档里明确推荐用它管理文件句柄、网络连接、流读取器、锁。Node.js 侧只要实现了 [Symbol.dispose] 的对象都可以用,不挑运行环境。

TypeScript 从 5.2 开始支持 using,Transpiler 模式一直是 TS 开发者管理资源的主流做法。现在 Safari 补上了,三大引擎都通,线上代码终于可以卸掉编译这一步直接跑。

现在能用在生产环境吗

主流环境覆盖情况:

  • Chrome / Edge:稳定支持(V8 内核)
  • Firefox:稳定支持(SpiderMonkey)
  • Safari:Technology Preview 250(2026-08-13),稳定版预计 2026 年内跟上
  • Node.js 22+:稳定支持

Safari 这块是唯一的预览状态。如果你的用户里 Safari/iOS 占比不高,现在就可以把 TypeScript 里的 using 编译降级关掉,直接写原生语法。如果占比高,可以用 Symbol.dispose polyfill + TypeScript 类型做渐进迁移,语法不变。

怎么迁移手上的代码

最直接的做法:把现有 try/finally 里的手动清理替换成 using/await using,把 [Symbol.dispose][Symbol.asyncDispose] 实现到你的资源包装类里。Node.js 的文件句柄、数据库连接池、缓存客户端基本都已经是 [Symbol.asyncDispose] 的实现方,直接 await using 就行。

三年 TS 写下来,using 一直是编译输出的代码。现在它回到 JavaScript 自己的语法里,try/finally 那套写法可以正式归档了。


相关链接:

评论区

0 条评论

登录后可评论。

阿柯·前端架构 14 阅读