TypeScript has ‘using’ now, but we should avoid using it

A-yon
4 min readSep 12, 2023

--

About half a month ago, TypeScript 5.2 was released. In this new version, it introduced a new keyword using , which apparently is borrowed from C#.

I’m not going to talk about what it promises or how to use it, rather, I’m going to diss it and talk about the reason why I’m suggesting we should prevent using it in our codebase.

The very first reason is that it looks odd when composite with async functions. We have to use await using , what the heck is that? Can’t the transpiler or the runtime detect that the resource is using Symbol.asyncDispose instead of Symbol.dispose and resolve the closure in async context automatically for us behind the scenes and allow us just use the pure using keyword and keep our code clean and understandable?

The second reason is that relying on the upstream APIs to implement the cleanup functions is unreliable, even library authors are willing to add such a feature, what happens to the users who are still using an old version of the library? Surely they can force the user to upgrade, just like some libraries now only support ES modules and no longer support CommonJS, but it’s bad, take nonoid for example.

Maybe upgrading a dependency is easy, but what about the Node.js runtime itself? Say if Node v20 implements the dispose API in the fs package, are we happy to use it? I don’t think so. As an active and enthusiastic developer, I like to try new things on my machine, so obviously, I run the newest version of Node.js. But my production environment isn’t the same, which still runs Node.js v16 for some reason, and it's not that easy to upgrade the production environment. What if I use the new using keyword in my codebase, and it works fine and I’m happy about it, but when I try to deploy the new updates, the production environment just stops working.

The third reason is that we don’t know if we should use this feature or not. Regarding the reasons above, if an upstream API returns an object that fulfills the new feature, but I’m concerned about compatibility issues, especially when I’m developing shared libraries that could potentially be used by different people in different environments, I’d like to support as many Node.js versions as I could. So I decided not to use the using keyword, then what should I use? I hope the upstream library would provide another API that doesn’t use the dispose feature, which it should not because that would be odd and inconsistent.

While other languages are getting better and better, TypeScript/JavaScript is getting weirder and weirder. Python 3.13 is going to drop GIL, enabling the language to support real multi-threading that can perform parallel tasks on multi-core machines. So I can’t help but ask, what’s wrong with the design of TypeScript today?

I’ve been coding in Golang for a while. In Golang, we have a defer keyword, which is a better solution for explicit resource management. It not only reduces the chance of forgetting to release the resource, but it supports a wider range of cleanup work, not just closing some files and disconnecting from the database, but it can also perform other tasks. For example, if we start a child process run in the background, we can immediately call the kill function with the defer keyword, and after the function is complete, it guarantees that the child process will be terminated as well.

TypeScript is known to adopt features from other languages, so why not adopt the defer keyword, I suppose the reason is that it’s a Microsoft thing, and has a stronger influence by C# instead of Go.

Luckily, JS is a very free language. Even though we cannot have native defer support, we can simulate it, and I’ve done so in my recent project. The child process I’ve mentioned above is actually a scenario in this project, that is used in the unit tests to spawn a child process to start a gRPC server that the program can connect to and stop once the test function completes. I’m not going to talk about the details of this project, but I’d like to post some pictures of the code that uses the defer feature in both TypeScript and Golang, it just looks simple and right.

traditional try…catch…
using defer
golang version

The following links are the repos that are mentioned in the pictures, take a look if you are interested.

https://github.com/ayonli/jsext

https://github.com/ayonli/goext

--

--

A-yon

Full-stack developer, love building tools, believe in Buddhism. https://ayon.li