好用的 TypeScript 新特性:using 关键字介绍

TypeScript 5.2 版本中新添加了 using 关键字,目前该关键字的提案也进入了 ECMAScript 的 Stage 3,也就是说很快就会进入 JavaScript 语言本身中。using 和我们非常熟悉的 const, letvar 一样都是用于变量声明的,那么它到底有什么与众不同的地方呢?

使用场景

假设我们现在写了一个打印日志到文件中的函数:

ts 复制代码
function log(content: string) {
  const file = fs.openSync("log.txt", "w+");

  file.writeFileSync(file, content);

  // 释放文件
  fs.closeSync(file);
}

非常简单的一个功能,但过了几天,组长要求我们把日志区分为普通日志和警告日志,此时我们需要再改造一下这个函数:

ts 复制代码
function log(content: string, isWarn?: boolean) {
  const file = fs.openSync("log.txt", "w+");

  file.writeFileSync(file, content);

  if (!isWarn) {
    return;
  }
  // isWarn 的日志添加警告标识
  file.writeFileSync(file, "[warn]");

  // 释放文件
  fs.closeSync(file);
}

这个时候运行函数会出现问题,因为在 isWarn 为 false 的情况下,我们直接 return 了,没有走到函数末尾 fs.closeSync(),这会导致文件连接没有释放。

虽然说上面这段代码的写法本身就有些问题,但在复杂的逻辑中确实很容易出现忘记释放文件、数据库连接这样的现象,而 using 关键字就能够很好地解决这类问题。

把上面的例子用 using 改写如下所示:

ts 复制代码
function log(content: string, isWarn?: boolean) {
  using file = fs.openSync("log.txt", "w+");

  file.writeSync(file, content);

  if (!isWarn) {
    return;
  }
  // isWarn 的日志添加警告标识
  file.writeSync(file, "[warn]");
}

可以看到我们甚至不需要在函数末尾调用 fs.closeSync(),这是因为 using 关键字声明的变量会在被释放时执行一些额外的操作,有点类似于 C++ 中的析构函数。在这个例子中我们 using 的是 node.js 原生的 openSync 函数返回的变量,它会在变量被删除时自动释放文件的连接,这样我们就不需要自己手动进行管理了,减少了犯错误的可能。

使用方法

上面的例子中,我们不需要任何额外操作就获得了自动释放文件的能力,这是因为标准库已经帮我们定义好了文件变量被释放时的行为。如果是我们自己写的功能,则需要自己对变量释放时要做的操作进行定义。

假设我们自己写了一个打开文件的函数:

ts 复制代码
function openFile(path) {
  console.log("打开文件:", path);
  const file = fs.openSync(path, "w+");

  return {
    handle: file,
    [Symbol.dispose]() {
      console.log("释放文件:", path);
      fs.closeSync();
    },
  };
}

返回对象中的 [Symbol.dispose]() 会在变量被回收时调用(如果变量是用 using 声明的),这样我们就不需要让用户手动释放文件了,保证了程序的正确性。

我们来看看使用这个函数的实例:

ts 复制代码
using a = openFile("a")
fs.writeSync(file, "hello");

运行该代码会打印:

css 复制代码
打开文件:a
释放文件:a

当我们调用 openFile() 创建变量 a 时,我们会打印 打开文件:a 并把文件的描述符返回。当程序运行结束时,变量 a 的生命周期结束,[Symbol.dispose]() 被调用,打印 释放文件:a 并关闭文件连接。

我们再看一个复杂点的例子:

ts 复制代码
using a = openFile("a")
using b = openFile("b")
{
  using c = openFile("c")
  using d = openFile("d")
}
using e = openFile("e")

大家可以停下来想一想,这段程序会打印出什么样的内容?

答案是:

css 复制代码
打开文件:a
打开文件:b
打开文件:c
打开文件:d
释放文件:d
释放文件:c
打开文件:e
释放文件:b
释放文件:a

可以看到,每个变量在其生命周期结束时就调用了它的 [Symbol.dispose]() 函数,因为 cd 被放在块级作用域中,所以比外层的 a, be 更快被释放。

在处理这类需要释放连接的功能时,最怕的就是中间的某一步代码停止了整个函数的运行,比如说中途 throw 了一个错误,然后释放连接的代码就被跳过了。这种情况下 using 关键字也能够很好的完成任务。以下面的代码为例:

ts 复制代码
using a = openFile("a")
using b = openFile("b")
{
  using c = openFile("c")
  throw new Error()
  using d = openFile("d")
}
using e = openFile("e")

此时控制台打印如下:

css 复制代码
打开文件:a
打开文件:b
打开文件:c
释放文件:c
释放文件:b
释放文件:a

可以看到抛出错误时,已经被打开的几个文件也正确地关闭了,这就是 using 关键字的功劳。

异步 using

我们刚刚处理文件用的是同步方法,但这并不是最佳实践,通常我们会使用异步的方法来进行文件的操作。但是 [Symbol.dispose]() 必须是同步的函数,这个时候我们就要用到另外一个 Symbol,它的名字就是......没错,[Symbol.asyncDispose]()

我们用异步的方式改写一下刚刚的 openFile() 函数:

ts 复制代码
function openFile(path) {
  console.log("打开文件:", path);
  const file = fs.openSync(path, "w+");

  return {
    handle: file,
    async [Symbol.asyncDispose]() {
      console.log("释放文件:", path);
      await fs.close();
    },
  };
}

这个时候我们需要改变一下使用 using 关键字的方式,在 using 之前添加 await

ts 复制代码
async (() => {
  await using a = openFile("a")
  await using b = openFile("b")
})()

这里的 await 关键字具有一定的迷惑性,看似是变量声明语句被 await 了,实际上并没有。我们加一行 log 试试:

ts 复制代码
async (() => {
  await using a = openFile("a")
  console.log("haha")
  await using b = openFile("b")
})()

输出:

css 复制代码
打开文件:a
haha
打开文件:b
释放文件:b
释放文件:a

可以发现 openFile()console.log() 是同步执行的。那么异步的是什么呢?当然就是 [Symbol.asyncDispose]() 函数了,但因为它是在函数最后隐式执行的,所以我们感觉不到。

总结

using 是一个新的变量声明关键字,它能够帮助我们更好地管理需要释放的资源,减少代码出错的概率,是非常实用的一个新特性。目前该特性已经得到 TypeScript 新版本的支持,JavaScript 的原生支持也指日可待了。

相关推荐
大家的林语冰19 分钟前
✌️ Deno 2.9 来了,ESM 直接导入 CSS,甚至能开发桌面应用?!
前端·javascript·node.js
不简说1 小时前
JS 代码技巧 vol.1 — 10 个让代码少写 30% 的小套路
前端·javascript·面试
@@@@@@@@541 小时前
JavaScript 的缺点与 TypeScript 的弥补
开发语言·javascript·typescript
Hilaku1 小时前
为什么业务型前端容易遭遇中年危机?
前端·javascript·程序员
এ慕ོ冬℘゜1 小时前
深度解析 JavaScript:赋予 Web 灵魂的“全栈语言”
开发语言·前端·javascript
iCOD3R1 小时前
Skill - 3秒生成精美GitHub主页
前端·程序员·ai编程
爱勇宝1 小时前
《道德经》第5章:生产环境不相信眼泪
前端·后端·架构
掘金者阿豪1 小时前
LEFT JOIN 凭空消失的背后,是优化器偷偷帮你做了决定
前端·后端
HackTwoHub1 小时前
AntiDebug Mcp、AI逆向绕过前端,Hook 加密接口,抓取 Vue 路由漏洞,接入 MCP 让 AI 自动化挖掘前端漏洞
前端·vue.js·人工智能·安全·web安全·网络安全·系统安全
Mr_凌宇1 小时前
AI 应用工程进阶扩展学习笔记 二
前端·面试