Using using in TypeScript for resource management
Tags: typescript, webdev
Published: 2023-09-05 15:29:23

**Written by [Lewis Cianci](https://blog.logrocket.com/author/lewiscianci/)✏️**

When it comes to software development, everyone is most interested in what the actual end product can do. How many people plan to use the app every day? What is its marketing potential? 

The big picture — the end product’s usefulness and profitability — is what gets the most attention. However, in reality, producing and shipping an app like that is the result of making lots of high-quality, yet comparatively small decisions along the way. 

Investing in thoughtful strategy early on leads to maintainable applications that are less likely to experience weird bugs or problems when scaled up. One crucial strategic area is resource management, which is a seemingly small but immensely important aspect of software development. 

In this article, we will discuss the importance of resource management and how TypeScript’s new `using` operator can help us manage our resources better. 

Jump ahead:

*   [What is resource management?](#what-resource-management)
*   [The problem with current resource management strategies](#problem-current-resource-management-strategies)
*   [The `using` operator as a solution for better resource management](#using-operator-solution-better-resource-management)
*   [Using the `using` operator in TypeScript](#using-using-operator-typescript)
*   [What about resource management for `async` operations?](#resource-management-async-operations)
*   [Final thoughts: TypeScript and the future of resource management](#final-thoughts-typescript-future-resource-management)

## What is resource management? <a name="what-resource-management">

We program within finite environments, with finite amounts of processing power and memory. To that end, we can’t forego resource management. 

Resource management helps us avoid taking up unlimited amounts of memory and processing power. Without proper resource management, our apps would definitely run slowly and eventually crash. 

Within software, whenever we want to do something, we call a function. Sometimes, the lifetime of the object that we are calling the function on is static — in other words, it only exists once in memory and can be called at any time, for any reason. 

An example of a function like this could be if we were to call `console.log('log output')` in our developer console within Chrome or Edge. To do this, we don’t even need to construct a new `console` object. It’s already globally available within our browser window: ![Screenshot Of Developer Console Showing A Simple Example Of A Console Object To Demonstrate Calling A Function In Software](http://blog.logrocket.com/wp-content/uploads/2023/08/Simple-example-console-object-demonstrate-calling-function.png) 

We don’t really need to manage this resource for various reasons:

*   It’s created outside of our control
*   It exists as a globally accessible function
*   It’s also completed within nanoseconds
*   It doesn’t run asynchronously

But not all of our apps are this simple. With modern web apps, we can do grand things — call web services on the other side of the world, send our images for AI analysis, write detailed responses to a database — the list goes on. 

However, in doing so, we make heavy use of system resources. As a result, it’s up to us to appropriately manage these resources as time goes on.

## The problem with current resource management strategies <a name="problem-current-resource-management-strategies">

Let’s imagine that we want to connect to a database, write some records, and then disconnect from the database. This would probably equate to roughly the following:

```typescript
let db = new DbConnection("server=localhost,token=<...token here...>");
await db.connect();
await db.execute("INSERT INTO testusers ('Rob')");
await db.close(); // clean up used resourceso
```

We’ve instantiated our `DbConnection`, connected to it, and have also inserted some data. Because these objects have been created, they are now taking up some memory on the host system. 

After this takes place, we need to clean up after ourselves, which involves disconnecting from the database and indicating that the system can dispose of the `DbConnection` object. Depending on where you are using this code, you might use something like `db.close()` or `db.dispose()` on the following line. 

Assuming everything works correctly, we’ll connect to the database, the data will be inserted, and the resources will get cleaned up. But, as we know in software development, a number of things can go wrong in a normal environment, including shaky connections, server crashes, and more. 

If something goes wrong, we can just use a `try...catch` block to handle issues and perform the appropriate cleanup actions, like so:

```typescript
let db = new DbConnection("server=localhost,token=<...token here...>");
try{
  await db.connect();
  await db.execute("INSERT INTO testusers ('Rob')");
}
finally
{
  await db.close();
}
```

But even in this case, our code still isn’t technically correct. 

Our `db` object would be in a `closed` state because the code within the `finally` block has been executed. Later on in this file, we might forget that we’ve already closed this object and attempt to call `db` again, only to have our app throw an exception. 

These issues increase if we have other objects that depend on the `DbConnection`, which would need to be disposed of first before we can dispose of the underlying objects. While this is certainly not impossible to do at the moment, it can make our code noisier.

## The `using` operator as a solution for better resource management <a name="using-operator-solution-better-resource-management">

Enter the [explicit resource management proposal](https://github.com/tc39/proposal-explicit-resource-management/tree/main), which describes — among many other things — a new `using` operator that was introduced in TypeScript 5.2 and is making its way into JavaScript. From the top of the `README` file, here’s what this proposal aims to do:

> This proposal intends to address a common pattern in software development regarding the lifetime and management of various resources (memory, I/O, etc.). This pattern generally includes the allocation of a resource and the ability to explicitly release critical resources.

It sounds good, but what does it mean? Well, it’s a little complicated, so let’s use common concepts to understand what this change means to us. 

Consider the humble [constructor in TypeScript](https://blog.logrocket.com/writing-constructor-typescript/). Whenever we instantiate an object within TypeScript, the constructor runs as the object is “constructed.” We’re always guaranteed that the constructor will run when an object is created. 

After the object is constructed and we have carried out the appropriate operation, we likely have some teardown to do — cleaning up resources, closing connections, etc. How do we do that? With TypeScript 5.2, we can use the `using` operator. 

Imagine that we have a table `insert` within a function. Let’s see how we can write this out with the `using` operator:

```typescript
function insertTestUsers() {
  using db = DbConnection("server=localhost,token=<...token here...>");
  await db.connect();
  await db.execute("INSERT INTO testusers ('Rob')");
}
```

By now, if you’re like me, you’ve been using TypeScript for quite some time. You probably already know most of the syntactical keywords that you’re likely to use in your day-to-day. But the `using` keyword sticks out as new here. 

If you’ve ever used C#, you may recognize `using` and already understand that it leads to much easier resource management and cleanup. Since C# and TypeScript are pretty closely related in terms of who invented them, it’s perhaps no surprise to see similar keywords being used for similar functions. 

But that’s in C# — how do we use it in TypeScript? Well, as of 22 August 2023, it’s currently available in the new TypeScript 5.2, so you should be able to use it today, as long as you update your installed version of TypeScript!

## Using the `using` operator in TypeScript <a name="using-using-operator-typescript">

Fortunately, setting up to use the `using` operator is quite simple. Start by creating a new directory, adding a new `tsconfig.json` to the directory, and pasting the following within the file:

```json
{
    "compilerOptions": {
        "target": "es2022",
        "lib": ["es2022", "esnext.disposable", "dom"],
        "module": "es2022"
    },
}
```

If you’re like me, you might get a squiggly line under the `ESNext.Disposable` entry. It’s okay to ignore it. 

Next, within the directory, run the following command:

```bash
npm -i typescript --dev
```

If you run `tsc` `--version`, it should return TypeScript 5.2\. That means you’re good to go 🎉 

Now, let’s create a couple of classes:

*   One class will be responsible for writing to the database
*   The other class will be responsible for writing to the log, which is in the database. The logger will have a dependency on the database

We’ll also include an import to `core-js` so we can use the `dispose` functionality before it becomes widely available:

```typescript
import 'core-js/actual/index.js'

class FakeDatabaseWriter {
    connected = false;
    executeSQLStatement(statement: string) {
        console.log(`Pretending to execute statement ${statement}`)
    }
    connect(){
        this.connected = true;
    }
}
class FakeLogger {
    constructor(private db: FakeDatabaseWriter){
    }
    writeLogMessage(message: string){
        console.log(`Pretending to log ${message}`);
    }
}
```

Now, for the secret sauce of this tutorial! Let’s make both of these classes implement `Disposable` and then implement the requirements of this interface, which is a new function called `Symbol.dispose`. 

Within these functions, let’s carry out some logging that describes when these objects are constructed and when they are disposed of for the example we’re using. Our code now looks like this:

```typescript
// Because dispose and asyncDispose are so new, we need to manually 'polyfill' the existence of these functions in order for TypeScript to use them
// See: https://devblogs.microsoft.com/typescript/announcing-typescript-5-2/#using-declarations-and-explicit-resource-management
(Symbol as any).dispose ??= Symbol("Symbol.dispose");
(Symbol as any).asyncDispose ??= Symbol("Symbol.asyncDispose");

class FakeDatabaseWriter implements Disposable {
    constructor(){
        console.log("The FakeDatabaseWriter is being constructed");
    }
    [Symbol.dispose](): void {
        console.log("The FakeDatabaseWriter is disposing! Setting this.connected to false.");
        this.connected = false;
        console.log(`Connected property is now ${this.connected}`);
    }
    connected = false;
    executeSQLStatement(statement: string) {
        console.log(`Pretending to execute statement ${statement}`)
    }
    connect(){
        this.connected = true;
    }
}
class FakeLogger implements Disposable {

    [Symbol.dispose](): void {
        console.log("The FakeLogger is disposing!");
    }
    constructor(private db: FakeDatabaseWriter){
        console.log("The FakeLogger is being constructed");
    }

    writeLogMessage(message: string){
        console.log(`Pretending to log ${message}`);
    }
}
```

With all of this now set up, let's create our classes and see what happens. 

We can use the new `using` keyword to instantiate our `FakeDatabaseWriter` and then immediately also instantiate a new `FakeLogger` with the already-created database writer. Then, we can connect and execute the statement as required:

```typescript
{
    using db = new FakeDatabaseWriter(), logger = new FakeLogger(db);
    db.connect();
    db.executeSQLStatement("INSERT INTO fakeTable VALUES ('value one', 'value two')");
}
```

Now run `npm run exec`. The output is as follows:

```bash
The FakeDatabaseWriter is being constructed
The FakeLogger is being constructed
Pretending to execute statement INSERT INTO fakeTable VALUES ('value one', 'value two')
The FakeLogger is disposing!
The FakeDatabaseWriter is disposing! Setting this.connected to false.
Connected property is now false
```

The order of these operations is important. To summarize:

1.  Objects in the `using` statement are created
2.  Code is executed on subsequent lines
3.  When the `using` statement goes out of scope, the `dispose` function is called on objects created via the `using` statement

Note that the disposals are called in reverse order. This is key! By calling the disposals in reverse order, dependent objects are disposed of first, before other underlying objects. 

The result of this is that everything is constructed and disposed of cleanly, without having to remember to do so in `try...catch...finally` blocks.

## What about resource management for `async` operations? <a name="resource-management-async-operations">

Our previous code samples in this article concerned functions that run entirely synchronously, or block the calling function. For functions that complete extremely quickly, this is perfectly acceptable. 

The reality, however, is that we are very likely to encounter operations that will require an asynchronous cleanup as we wait for resources to be freed up. In these cases, we can implement `AsyncDisposable`, like this:

```typescript
class LongRunningCleanup implements AsyncDisposable{
    async [Symbol.asyncDispose]()  {
        console.log(`LongRunningCleanup is disposing! Began at ${new Date()}`)
        await new Promise(resolve => setTimeout(resolve, 1000));
        console.log(`LongRunningCleanup is finished disposing. Finished at ${new Date()}`)
    }
    async longRunningOperation() {
        console.log("Executing long running operation...");
        await new Promise(resolve => setTimeout(resolve, 1000));
        console.log("Long running operation has finished");
    }
}
```

Executing this code has the following results:

```bash
The FakeDatabaseWriter is being constructed
The FakeLogger is being constructed
Pretending to execute statement INSERT INTO fakeTable VALUES ('value one', 'value two')
Executing long running operation...
LongRunningCleanup is disposing! Began at Mon Jul 31 2023 23:09:34 GMT+1000 (Australian Eastern Standard Time)
Long running operation has finished
LongRunningCleanup is finished disposing. Finished at Mon Jul 31 2023 23:09:35 GMT+1000 (Australian Eastern Standard Time)
The FakeLogger is disposing!
The FakeDatabaseWriter is disposing! Setting this.connected to false.
Connected property is now false
```

Again, we see the dependencies are freed in reverse order. The difference here is that the call to the asynchronous disposal method is called and the `await` is observed. 

The complete code sample is this:

```typescript
// Because dispose and asyncDispose are so new, we need to manually 'polyfill' the existence of these functions in order for TypeScript to use them
// See: https://devblogs.microsoft.com/typescript/announcing-typescript-5-2/#using-declarations-and-explicit-resource-management
(Symbol as any).dispose ??= Symbol("Symbol.dispose");
(Symbol as any).asyncDispose ??= Symbol("Symbol.asyncDispose");

class FakeDatabaseWriter implements Disposable {
    constructor() {
        console.log("The FakeDatabaseWriter is being constructed");
    }
    connected = false;
    executeSQLStatement(statement: string) {
        console.log(`Pretending to execute statement ${statement}`)
    }
    connect() {
        this.connected = true;
    }
    [Symbol.dispose]() {
        console.log("The FakeDatabaseWriter is disposing! Setting this.connected to false.");
        this.connected = false;
        console.log(`Connected property is now ${this.connected}`);
    }
}
class FakeLogger implements Disposable {
    [Symbol.dispose]()  {
        console.log("The FakeLogger is disposing!");
    }
    constructor(private db: FakeDatabaseWriter) {
        console.log("The FakeLogger is being constructed");
    }

    writeLogMessage(message: string) {
        console.log(`Pretending to log ${message}`);
    }
}
class LongRunningCleanup implements AsyncDisposable{
    async [Symbol.asyncDispose]()  {
        console.log(`LongRunningCleanup is disposing! Began at ${new Date()}`)
        await new Promise(resolve => setTimeout(resolve, 1000));
        console.log(`LongRunningCleanup is finished disposing. Finished at ${new Date()}`)
    }
    async longRunningOperation() {
        console.log("Executing long running operation...");
        await new Promise(resolve => setTimeout(resolve, 1000));
        console.log("Long running operation has finished");
    }
}

using db = new FakeDatabaseWriter(), logger = new FakeLogger(db);
await using longrunning = new LongRunningCleanup();
db.connect();
db.executeSQLStatement("INSERT INTO fakeTable VALUES ('value one', 'value two')");
longrunning.longRunningOperation();

export {}
```

## Final thoughts: TypeScript and the future of resource management <a name="final-thoughts-typescript-future-resource-management">

Using the new `using` statement should make resource management in TypeScript much easier, and you won’t have to keep remembering to call `dispose` manually. Goodbye, memory leaks! 

At the same time, this change doesn’t cause any regressions or break backward compatibility, so you can hopefully start using it without worrying too much. Enjoy your streamlined resource management! 😎🏢

---

##[LogRocket](https://lp.logrocket.com/blg/typescript-signup): Full visibility into your web and mobile apps

[![LogRocket Signup](https://blog.logrocket.com/wp-content/uploads/2017/03/1d0cd-1s_rmyo6nbrasp-xtvbaxfg.png)](https://lp.logrocket.com/blg/typescript-signup)

[LogRocket](https://lp.logrocket.com/blg/typescript-signup) is a frontend application monitoring solution that lets you replay problems as if they happened in your own browser. Instead of guessing why errors happen, or asking users for screenshots and log dumps, LogRocket lets you replay the session to quickly understand what went wrong. It works perfectly with any app, regardless of framework, and has plugins to log additional context from Redux, Vuex, and @ngrx/store.

In addition to logging Redux actions and state, LogRocket records console logs, JavaScript errors, stacktraces, network requests/responses with headers + bodies, browser metadata, and custom logs. It also instruments the DOM to record the HTML and CSS on the page, recreating pixel-perfect videos of even the most complex single-page and mobile apps.

[Try it for free](https://lp.logrocket.com/blg/typescript-signup).