Safari 把 using 原生化了,TypeScript 团队可以停掉一个 transpile 插件
Safari 在 2026 年 8 月 13 日发布了 Safari Technology Preview 250,把 using 和 await 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 风格——资源生命周期交给词法作用域,不再依赖开发者记住清理顺序。
DisposableStack 和 AsyncDisposableStack 是这批提案里被严重低估的部分。适用场景:资源是动态创建的,清理顺序也不固定。比如:
{
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 重写,看代码是不是真的变短了、逻辑是不是更好读了。如果变短了——那就是这件事真正的价值;如果没有,那至少现在你知道边界在哪了。
评论区
登录后可评论。