一、安装最新版的 TypeScript
ts
cd your_dir
pnpm init
pnpm add typescript # 当前的 ts 版本是 `5.2.2`
当然可以使用最新的 bun/deno
等运行时直接支持 typescript>
二、提案
📕ECMAScript 显示资源管理 一个在对象上执行显式资源清理的方法。由
using
声明和DisposableStack
对象的语义调用。
三、初始化
sh
npx tsc --init
由于 TS 需要编译成 JS 才能运行,在各大 javascript 运行时中。
四、修改配置
json
{
"compilerOptions": {
"lib": ["ES2015", "DOM", "ESNext"],
}
}
在 lib 中至少 ES2015
和 ESNext
,因为 Symbol.dispose
目前还处在 lib.esnext.dispable.d.ts
中。
五、Symbol.dispose 定义方法
ts
{
const getResource = () => {
return {
[Symbol.dispose]: () => {
console.log('Hello World!')
}
}
}
}
🎡 Symbol.dispose
在浏览中已经能够使用。
六、Symbol.asyncDispose 定义异步方法
ts
const getResource = () => ({
[Symbol.asyncDispose]: async () => {
await someAsyncFunc();
},
});
{
await using resource = getResource();
}
七、using 使用方法
using 是一个关键字,与 var/let/const
类似,在 c#
中也有类似的关键字 设计
ts
{
const getResource = () => {
return {
[Symbol.dispose]: () => {
console.log('Hello World!')
}
}
}
using resource = getResource();
}
八、作用
- 数据库链接关闭
- 文件句柄
- 订阅关闭
九、替换 try-finally
ts
import { open } from "node:fs/promises";
let filehandle;
try {
filehandle = await open("thefile.txt", "r");
} finally {
await filehandle?.close();
}
ts
import { open } from "node:fs/promises";
const getFileHandle = async (path: string) => {
const filehandle = await open(path, "r");
return {
filehandle,
[Symbol.asyncDispose]: async () => {
await filehandle.close();
},
};
};
{
await using file = getFileHandle("thefile.txt"); // 离开此作用域之后自动调用 close
}
getFileHandle 函数打开文件,getFileHandle 且返回了 [Symbol.asyncDispose]
, 配合 using 关键使用,解决了 try-finally
层级的代码。代码的创建与销毁的逻辑放在了 getFileHandle
函数内部,更加符合函数时编程的范式。
十、在类中使用
- 全局的
Disposable
和AsyncDisposable
异步版本。
ts
class MyResouse implements Disposable {
#name = 'a';
#path = '/';
constructor(name: string, path: string) {
this.#name = name;
this.#path = path;
}
clear(name, path) {
// your clear logic
}
[Symbol.dispose]() {
this.clear(this.#name, this.#path)
}
}
- 使用
ts
{
using file = new TempFile("lucy", "/list/lucky");
// use file...
if (someCondition()) {
// do some more work...
return;
}
}
十一、DisposableStack/AsyncDisposableStack
ts
function doSomeWork() {
const path = ".some_temp_file";
const file = fs.openSync(path, "w+");
using cleanup = new DisposableStack();
cleanup.defer(() => {
fs.closeSync(file);
fs.unlinkSync(path);
});
// use file...
if (someCondition()) {
// do some more work...
return;
}
// ...
}
在这里,该defer()
方法只接受一个回调,并且该回调将在cleanup
被处理后运行。这样做的好处是不在需要函数的返回值了。
十二、错误处理
ts
class ErrorA extends Error {
name = "ErrorA";
}
class ErrorB extends Error {
name = "ErrorB";
}
function throwy(id: string) {
return {
[Symbol.dispose]() {
throw new ErrorA(`Error from ${id}`);
}
};
}
function func() {
using a = throwy("a");
throw new ErrorB("oops!")
}
try {
func();
}
catch (e: any) {
console.log(e.name); // SuppressedError
console.log(e.message); // An error was suppressed during disposal.
console.log(e.error.name); // ErrorA
console.log(e.error.message); // Error from a
console.log(e.suppressed.name); // ErrorB
console.log(e.suppressed.message); // oops!
}
using
声明应该能够应对异常;如果抛出错误,则在处理后重新抛出。另一方面,函数体可能会按预期执行,但Symbol.dispose
可能会抛出异常。在这种情况下,该异常也会被重新抛出。
但是,如果处理之前和处理期间的逻辑都抛出错误,会发生什么情况?对于这些情况,SuppressedError
已作为 的新子类型引入Error
。它具有一个保存suppressed
最后抛出的错误的属性和一个保存error
最近抛出的错误的属性。
十三、小结
本文主要讲解了 using
新特性 显示资源管理
,在浏览中 Symbol.dispose
已经可以使用,但是在 node.js 等环境中依然不同使用,包含 using
, 新特性的普及需要一定的时间和验证。