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

相关推荐
疯狂的沙粒4 分钟前
Vue 前端大屏做多端屏幕适配时,如何让其自动适配多种不同尺寸的屏幕?
前端·javascript·vue.js
范小多7 分钟前
24小时学会Python Visual code +Python Playwright通过谷歌浏览器取控件元素(连载、十一)
服务器·前端·python
ooolmf8 分钟前
matlab2024读取温度01
java·前端·javascript
打工人小夏9 分钟前
前端vue3项目使用nprogress动画组件,实现页面加载动画
前端
一颗宁檬不酸11 分钟前
前端农业商城中产品产地溯源功能的实现
前端
李少兄18 分钟前
深入理解前端中的透视(Perspective)
前端·css
席之郎小果冻20 分钟前
【03】【创建型】【聊一聊,单例模式】
开发语言·javascript·单例模式
江公望28 分钟前
HTML5 History 模式 5分钟讲清楚
前端·html·html5
云和数据.ChenGuang34 分钟前
Zabbix Web 界面安装时**无法自动创建配置文件 `zabbix.conf.php`** 的问题
前端·zabbix·运维技术·数据库运维工程师·运维教程
码界奇点37 分钟前
Java Web学习 第15篇jQuery万字长文详解从入门到实战解锁前端交互新境界
java·前端·学习·jquery