你以为 try/finally 已经够用了?今天 using 关键字把这件事彻底原生化了
写过 JavaScript 的人都踩过这个坑——每次写 try/finally 只是为了释放一个资源,今天 using 关键字把这件事彻底原生化了。
你写过多少次这样的代码?
const file = fs.openSync('config.json', 'r');
try {
const content = file.readFileSync();
return JSON.parse(content);
} finally {
file.close(); // 这行漏了,文件句柄就泄漏了
}
一个资源的获取和释放,分散在函数的两头。中间一旦加了 early return、加了 throw 路径,finally 块就要跟着改。漏一行,资源就泄漏了。
这个问题存在了二十多年。终于,JavaScript 自己学会清理了。
using 怎么用
ES2026(TC39 Stage 4,2025 年 5 月正式固化)引入了 using 声明。对象只要带 [Symbol.dispose]() 方法,用 using 声明后,作用域退出时自动调用:
import { open } from 'node:fs/promises';
async function readConfig() {
await using file = await open('config.json', 'r');
const { buffer } = await file.read();
return JSON.parse(buffer);
} // file[Symbol.asyncDispose]() 自动调用,无论正常返回还是抛异常
await using 处理异步清理(数据库连接、流关闭)。using 处理同步清理(文件句柄、锁)。
多个资源按声明逆序释放
await using conn = await openConnection(); // 第二个释放
await using tx = await conn.beginTransaction(); // 第一个释放
await tx.query('SELECT 1');
// 退出时:先关 tx,再关 conn——依赖顺序自动保证
自己写一个 Disposable
只要对象有 [Symbol.dispose](),就能参与 using:
class ManagedTimer {
#id;
constructor(fn, ms) { this.#id = setTimeout(fn, ms); }
[Symbol.dispose]() { clearTimeout(this.#id); }
}
function processWithTimeout(fn, ms) {
using _timer = new ManagedTimer(fn, ms);
// 无论 processItems 怎么退出,timer 都会被清理
}
事件监听器也可以这么包装:
function onHover(el, enter, leave) {
el.addEventListener('mouseenter', enter);
el.addEventListener('mouseleave', leave);
return {
[Symbol.dispose]() {
el.removeEventListener('mouseenter', enter);
el.removeEventListener('mouseleave', leave);
}
};
}
DisposableStack:动态资源组合
运行时要注册多个未知数量的资源?DisposableStack 来帮忙:
async function processFiles(paths) {
await using stack = new AsyncDisposableStack();
const handles = await Promise.all(
paths.map(p => stack.use(await open(p, 'r')))
);
await transformAll(handles);
} // stack 退出时,所有文件句柄按逆序全部关闭
stack.use(resource) 注册并返回资源,stack.move() 可以把资源转移到另一个栈,防止自动释放。
SuppressedError:cleanup 失败不再吞掉
如果你的业务代码和 cleanup 代码同时抛异常,JavaScript 会把两个错误包在一个 SuppressedError 里,不会再有任何异常被静默吞掉。
现在在哪能用
| 环境 | 支持版本 |
|---|---|
| Chrome / Edge | 134+(2025 年 3 月) |
| Firefox | 141+(当前稳定版 152) |
| Safari | 18.2+(桌面) |
| Node.js | 24+(V8 13.6) |
| Deno | 1.37+ |
| Bun | 原生支持 |
| TypeScript | 5.2+(会转译到 older target) |
| Babel | 7.28+(默认开启) |
桌面浏览器全覆盖,移动端 Safari(iOS)还在追赶。生产环境用 TypeScript 或 Babel 编译即可,行为完全正确。
怎么上手
搜一下你代码库里有多少 finally {.*close|release|dispose 的模式——那些就是最优先可以迁移的目标。给资源类加上 [Symbol.dispose](),把 cleanup 从 finally 块移到资源获取那一行,作用域自己会处理剩下的。
这不是魔法,是 JavaScript 和资源对象之间的一个明确契约:实现了 Symbol.dispose,引擎负责在正确的时机替你调用。try/finally 解决的问题,using 用一行声明解决了;try/finally 没解决好的(多资源顺序、cleanup 失败吞异常),using 反而处理得更干净。
评论区
登录后可评论。