好用的 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 的原生支持也指日可待了。

相关推荐
崔庆才丨静觅5 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby60616 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了6 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅6 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅6 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅7 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment7 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅7 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊7 小时前
jwt介绍
前端
爱敲代码的小鱼7 小时前
AJAX(异步交互的技术来实现从服务端中获取数据):
前端·javascript·ajax