using: A keyword that make be easily that resource variable management (JavaScript & TypeScript)
Tags: typescript, javascript
Published: 2023-09-20 11:43:45

> The opinions expressed in this article are based on individual subjective views. Since the content of this article may contain errors, your understanding is appreciated. If you find any mistakes, differences in understanding, or have alternative opinions, comments are highly welcome!

# Overview

In JavaScript, in addition to the keywords `var`, `let`, and `const`, a new keyword `using` is soon to be added for variable initialization.

Variables declared with the `using` keyword will be treated as resource variables, allowing them to automatically perform resource cleanup before going out of scope. This is expected to simplify the cumbersome task of writing explicit resource cleanup code. The ECMAScript Proposal is currently at Stage 3, and you can track its status [here](https://github.com/tc39/proposal-explicit-resource-management#status). It will become a standard when it reaches Stage 4. For more details, please refer to [The TC39 Process](https://tc39.es/process-document/).

Additionally, TypeScript 5.2 introduces support for the `using` keyword. Please note that you may currently need polyfill code for this feature.

This article will cover the following topics:

- Basic usage
- Addressing previous challenges and solutions
- How to use `using` in TypeScript 5.2 with code examples

# Sample Code

You can see sample code on TypeScript 5.2 in the following GitHub repository: [ts-explicit-resource-management](https://github.com/genie-oh/ts-explicit-resource-management).

# Background

When handling with external resources such as file operations, database operations, and network communication (sockets), it is necessary to explicitly handle resource open and close (expose and dispose) operations.

To summarize, explicit resource handling like the one mentioned above has posed a considerable burden on programmers. To address this challenge, the `using` keyword has been proposed and is expected to become a standard specification soon.

For example, when working with PostgreSQL using `node-postgres`, you need to explicitly handle `connect()` and `close()` operations. Failing to call `close()` can lead to issues where the DB connection's resources are not released, causing the process not to terminate.

Other programming languages like Java have features like `AutoClosable` to address this issue, but JavaScript has lacked such an official feature until now. Therefore, programmers have had to come up with various workarounds, with the most common pattern being using `try-finally` as shown below:

```typescript
// ... inside an async function
let client: Client;
try {
  client = new Client({...connectionInfo});
  await client.connect();
  // ... some logic
} catch (e) {
  // ... error handling
} finally {
  await client.close();
}
```

However, even with this approach, writing `try-finally` for each usage point or trying to avoid it and creating a Wrapper Class for automatic cleanup presented various challenges. One significant challenge was maintaining code consistency among team members when it came to resource management, which I personally found to be quite difficult.

# Basic Syntax

With the `using` keyword, you can declare resource variables that have a `Symbol.dispose` symbol key, which defines a callback function for resource cleanup. When a resource variable declared with `using` goes out of scope, the function defined as `Symbol.dispose` is automatically called, performing the resource cleanup.

Here's an example:

```typescript
const getResource = () => {
  // ... create initialization code for the resource you want to use
  return {
    // Variables used with 'using' have a symbol key 'Symbol.dispose'
    // that holds a callback function for resource disposal
    [Symbol.dispose]: () => {
      // ... create resource cleanup logic
    },
  };
};

function doWorkOnResource() {
  // Declare a resource variable with the 'using' keyword
  using resource = getResource();
  // ... some logic
  return;
  // When the 'using' variable goes out of scope, disposal is automatic
}
```

If you want to work with asynchronous operations, similar to `async/await`, you need to use `Symbol.asyncDispose`:

```typescript
const getResource = () => 
//...
    [Symbol.asyncDispose]: async () => {
      // ... write to dispose resource with await
    },
//...

async function doWorkOnResource() {
  await using resource = await getResource();
}
```

Additionally, you can implement this with classes by implementing the `Disposable` and `AsyncDisposable` interfaces:

```typescript
class SomeResource implements Dispoable {
  //...
  async [Symbol.dispose]() {
    //... write to dispose resource
  }
```

```typescript
class SomeResource implements AsyncDisposable {
  //...
  async [Symbol.asyncDispose]() {
    //... write to dispose resource with await
  }
```

# Use Cases

Here, we'll introduce an example of connecting to PostgreSQL using `node-postgres`, performing a select operation, and then closing the connection.

You can find sample code in the following GitHub repository: [ts-explicit-resource-management](https://github.com/genie-oh/ts-explicit-resource-management).

## pg-try-finally.example.ts

This example demonstrates resource handling using the traditional `try-catch-finally` approach:

```typescript
import * as dotenv from "dotenv";
dotenv.config();
import { Client } from "pg";

/**
 * @throws Error: errors from pg
 */
const isAvailableDBConnection = async () => {
  let client: Client;
  try {
    // Try to connect
    client = new Client({
      host: process.env.DB_HOST,
      database: process.env.DB_NAME,
      user: process.env.DB_USER,
      password: process.env.DB_PASSWORD,
      port: +process.env.DB_PORT,
    });
    await client.connect();

    // Query
    const res = await client.query("SELECT $1::text as message", [
      "Hello world!",
    ]);
    console.info(res.rows[0].message); // Hello world!

    // Return true if the query is successful
    return true;
  } catch (e) {
    console.error(e);
    throw e;
  } finally {
    console.debug("Try to client.end()");
    await client.end();
  }
};

isAvailableDBConnection();
```

## pg-asyncDispose-func.example.ts

This example demonstrates the usage of `using` and `asyncDispose`. It is implemented as an AsyncDisposable Function:

```typescript 
import * as dotenv from "dotenv";
dotenv.config();
import { Client } from "pg";

// 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");

/**
 * @returns: { client: pg.Client, [Symbol.asyncDispose]: dispose function }
 */
const getDBConnection = async () => {
  // Try to connect
 

 const client = new Client({
    host: process.env.DB_HOST,
    database: process.env.DB_NAME,
    user: process.env.DB_USER,
    password: process.env.DB_PASSWORD,
    port: +process.env.DB_PORT,
  });
  await client.connect();

  // Return resource as disposable
  return {
    client,
    [Symbol.asyncDispose]: async () => {
      console.debug("Try to client.end()");
      await client.end();
    },
  };
};

/**
 * @throws Error: errors from pg
 */
const isAvailableDBConnection = async () => {
  // Declare resource variable by the `using` keyword
  await using db = await getDBConnection();
  const res = await db.client.query("SELECT $1::text as message", [
    "Hello world!",
  ]);
  console.info(res.rows[0].message);
  // ...
  // Before going out of scope, the resource will be disposed by the function of [Symbol.asyncDispose]
};

isAvailableDBConnection();
```

## pg-asyncDispose-class.example.ts

This example demonstrates the usage of `using` and `asyncDispose`. It is implemented as an AsyncDisposable Class:

```typescript
import * as dotenv from "dotenv";
dotenv.config();
import { Client } from "pg";

// 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");

/**
 * Disposable class
 */
class DBConnection implements AsyncDisposable {
  private client: Client;

  private constructor() {
    this.client = new Client({
      host: process.env.DB_HOST,
      database: process.env.DB_NAME,
      user: process.env.DB_USER,
      password: process.env.DB_PASSWORD,
      port: +process.env.DB_PORT,
    });
  }

  /**
   * Factory method
   * @returns: new instance of DBConnection
   */
  static async of() {
    const instance = new DBConnection();
    await instance.connect();

    return instance;
  }

  /**
   * Implemented by the asyncDisposable interface
   */
  async [Symbol.asyncDispose]() {
    console.debug("Try to client.end()");
    await this.client.end();
  }

  getClient() {
    return this.client;
  }

  private async connect() {
    await this.client.connect();
  }
}

/**
 * @throws Error: errors from pg
 */
const isAvailableDBConnection = async () => {
  // Declare resource variable by the `using` keyword
  await using db = await DBConnection.of();
  const client = db.getClient()
  
  const res = await client.query("SELECT $1::text as message", [
    "Hello world!",
  ]);
  
  console.info(res.rows[0].message);
  // ...
  // Before going out of scope, the resource will be disposed by the function of [Symbol.asyncDispose]
};

isAvailableDBConnection();
```

# Conclusion (my opinion)

I think the most significant advantage of the `using` keyword is the standardization it brings. While the `using` keyword may not solve all the issues mentioned above, it is expected to bring significant improvements.

In my experience, maintaining code for "resource management" in team development has often been challenging due to differences in how team members approach it and potential oversights that lead to complex runtime problems.

Recently, I have been addressing this by adopting frameworks that allow for Dependency Injection (DI), managing external resources within Singleton or method-level lifecycles, and then standardizing the approach as a team. However, this approach also comes with its challenges, especially during initial development and achieving common understanding.

Additionally, the management of external resource variables, which goes beyond just databases, has been an ongoing challenge.

Therefore, the introduction of the `using` keyword as a standard feature is expected to naturally lead JavaScript and TypeScript programmers to acquire this knowledge. This, in turn, can simplify resource management in team development and help maintain code quality.

# References

- [ECMAScript:proposal-explicit-resource-management](https://github.com/tc39/proposal-explicit-resource-management#status)
- [Announcing TypeScript 5.2: using Declarations and Explicit Resource Management](https://devblogs.microsoft.com/typescript/announcing-typescript-5-2/#using-declarations-and-explicit-resource-management)
- [Resource management in TypeScript with the using keyword](https://blog.logrocket.com/resource-management-typescript-using-keyword/)
```