Safari 把 using 原生化了,TypeScript 团队可以停掉一个 transpile 插件

Safari 在 2026 年 8 月 13 日发布了 Safari Technology Preview 250,把 usingawait using 两个关键字彻底做进了 WebKit。这意味着什么?所有主流浏览器引擎——V8(Chrome/Node.js)、SpiderMonkey(Firefox)、WebKit(Safari)——现在都原生支持这个 TC39 Stage 4 提案了。TypeScript 团队从 5.2(2023 年)就开始 transpile 这套语法,等的就是这一天。

JavaScript 写资源清理,三年来全靠手写 try...finally。打开文件、处理流、拿锁——清理代码永远跟在业务逻辑后面,漏一行就是泄漏,而且一旦清理本身也抛出异常,最原始的错误就被吞掉了。using 把这件事倒过来:资源跟作用域绑定,离开作用域自动调 [Symbol.dispose](),不管你是正常返回还是抛异常,清理一定跑,错误一定报。

看个真实场景。读一个 fetch 流,传统写法:

async function readData(url) {
  const response = await fetch(url);
  const reader = response.body.getReader();
  // 业务逻辑...
  reader.releaseLock(); // 漏了这行?流被锁住,下一个请求继续等
}

一旦业务逻辑里抛了异常,releaseLock() 根本执行不到。用 using 重写:

async function readData(url) {
  const response = await fetch(url);
  using reader = response.body.getReader();
  // 业务逻辑,无论这里发生什么,reader 自动 releaseLock
}

这就是 RAII 风格——资源生命周期交给词法作用域,不再依赖开发者记住清理顺序。

DisposableStackAsyncDisposableStack 是这批提案里被严重低估的部分。适用场景:资源是动态创建的,清理顺序也不固定。比如:

{
  using stack = new DisposableStack();
  const conn1 = stack.use(db.connect());
  const conn2 = stack.use(cache.connect());
  // 条件分支里还能追加
  if (needsThirdParty) {
    stack.use(thirdParty.connect());
  }
  // 无论哪个分支走,块结束统一按 LIFO 顺序清理
}

SuppressedError 解决的是清理时也抛异常的问题。假设释放 conn1 时抛了错,此时 conn2 的清理如果也被 conn1 的异常打断,就会静默泄漏。SuppressedError 把两个错误都包住:error 字段是最新的,suppressed 字段是之前被吞掉的那个。

Iterator.zip() 和 Iterator.zipKeyed()(Joint Iteration 提案)也在这批 Safari TP 250 里一起完成。不用先转数组,懒迭代跨多个数据源拉链式遍历,内存占用降一个量级。

TypeScript 5.2 已经在 2023 年支持了 using 的类型检查,但一直要靠 Babel 或 tsc 自己的 transpile 把 using 翻译成 ES2026 可运行的代码。Safari TP 250 出来之后,V8(Chrome 144+)、SpiderMonkey(Firefox 139+)、WebKit(Safari TP 250+)全部就位,transpile 目标可以直接降到 ES2026,不用再插一层语法转换。

实际影响最直接的是这几类代码:Node.js 文件句柄和流(fs.createReadStream 配合 using 不再需要手动 .close()),数据库连接封装(每次查询完自动还连接),前端状态管理里临时订阅的取消,以及任何涉及锁或信号量的并发代码。

下一步:找一个项目里现有的 try...finally 块,用 using 重写,看代码是不是真的变短了、逻辑是不是更好读了。如果变短了——那就是这件事真正的价值;如果没有,那至少现在你知道边界在哪了。

评论区

0 条评论

登录后可评论。

小智·AI工具控 13 阅读