TypeScript新特性之using关键字的使用方法
作者:wzkMaster
使用场景
假设我们现在写了一个打印日志到文件中的函数:
function log(content: string) { const file = fs.openSync("log.txt", "w+"); file.writeFileSync(file, content); // 释放文件 fs.closeSync(file); }
非常简单的一个功能,但过了几天,组长要求我们把日志区分为普通日志和警告日志,此时我们需要再改造一下这个函数:
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
改写如下所示:
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
函数返回的变量,它会在变量被删除时自动释放文件的连接,这样我们就不需要自己手动进行管理了,减少了犯错误的可能。
使用方法
上面的例子中,我们不需要任何额外操作就获得了自动释放文件的能力,这是因为标准库已经帮我们定义好了文件变量被释放时的行为。如果是我们自己写的功能,则需要自己对变量释放时要做的操作进行定义。
假设我们自己写了一个打开文件的函数:
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
声明的),这样我们就不需要让用户手动释放文件了,保证了程序的正确性。
我们来看看使用这个函数的实例:
using a = openFile("a") fs.writeSync(file, "hello");
运行该代码会打印:
打开文件:a
释放文件:a
当我们调用 openFile()
创建变量 a
时,我们会打印 打开文件:a
并把文件的描述符返回。当程序运行结束时,变量 a
的生命周期结束,[Symbol.dispose]()
被调用,打印 释放文件:a
并关闭文件连接。
我们再看一个复杂点的例子:
using a = openFile("a") using b = openFile("b") { using c = openFile("c") using d = openFile("d") } using e = openFile("e")
大家可以停下来想一想,这段程序会打印出什么样的内容?
答案是:
打开文件:a
打开文件:b
打开文件:c
打开文件:d
释放文件:d
释放文件:c
打开文件:e
释放文件:b
释放文件:a
可以看到,每个变量在其生命周期结束时就调用了它的 [Symbol.dispose]()
函数,因为 c
和 d
被放在块级作用域中,所以比外层的 a
, b
和 e
更快被释放。
在处理这类需要释放连接的功能时,最怕的就是中间的某一步代码停止了整个函数的运行,比如说中途 throw 了一个错误,然后释放连接的代码就被跳过了。这种情况下 using
关键字也能够很好的完成任务。以下面的代码为例:
using a = openFile("a") using b = openFile("b") { using c = openFile("c") throw new Error() using d = openFile("d") } using e = openFile("e")
此时控制台打印如下:
打开文件:a
打开文件:b
打开文件:c
释放文件:c
释放文件:b
释放文件:a
可以看到抛出错误时,已经被打开的几个文件也正确地关闭了,这就是 using
关键字的功劳。
异步 using
我们刚刚处理文件用的是同步方法,但这并不是最佳实践,通常我们会使用异步的方法来进行文件的操作。但是 [Symbol.dispose]()
必须是同步的函数,这个时候我们就要用到另外一个 Symbol,它的名字就是……没错,[Symbol.asyncDispose]()
!
我们用异步的方式改写一下刚刚的 openFile()
函数:
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
:
async (() => { await using a = openFile("a") await using b = openFile("b") })()
这里的 await
关键字具有一定的迷惑性,看似是变量声明语句被 await
了,实际上并没有。我们加一行 log 试试:
async (() => { await using a = openFile("a") console.log("haha") await using b = openFile("b") })()
输出:
打开文件:a
haha
打开文件:b
释放文件:b
释放文件:a
可以发现 openFile()
和 console.log()
是同步执行的。那么异步的是什么呢?当然就是 [Symbol.asyncDispose]()
函数了,但因为它是在函数最后隐式执行的,所以我们感觉不到。
总结
using
是一个新的变量声明关键字,它能够帮助我们更好地管理需要释放的资源,减少代码出错的概率,是非常实用的一个新特性。目前该特性已经得到 TypeScript 新版本的支持,JavaScript 的原生支持也指日可待了。
以上就是TypeScript新特性之using关键字的使用方法的详细内容,更多关于TypeScript using关键字的资料请关注脚本之家其它相关文章!