好用的 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 小时前
一文简单入门 Axios
前端·axios·apifox
im_AMBER5 小时前
React 15
前端·javascript·笔记·学习·react.js·前端框架
How_doyou_do6 小时前
模态框的两种管理思路
java·服务器·前端
snow@li6 小时前
前端:前端/浏览器 可以录屏吗 / 实践 / 录制 Microsoft Edge 标签页、应用窗口、整个屏幕
前端·浏览器录屏·前端录屏·web录屏
李贺梖梖6 小时前
CSS学习
前端·css
蚂小蚁6 小时前
一文吃透:宏任务、微任务、事件循环、浏览器渲染、Vue 批处理与 Node 差异(含性能优化)
前端·面试·架构
狼性书生7 小时前
uniapp实现的Tab 选项卡组件模板
前端·uni-app·vue·组件·插件
吃饺子不吃馅7 小时前
前端画布类型编辑器项目,历史记录技术方案调研
前端·架构·github
许___7 小时前
el-table多选模式下跨分页保留当前页选项
javascript·vue.js