using : リソース変数の管理が楽になるキーワード (JavaScript&TypeScript)

:::note info
この記事の内容にある意見は、個人の主観的意見を前提とします。
記事の内容は間違いがあり得ますので、ご了承いただけると幸いです。内容の間違い、認識の違い、違う意見などありましたら、コメント大歓迎です！
:::

# 概要

JavaScriptにおいて、変数を初期化できるkeyword `var, let, const`に加え、`using`がまもなく追加される予定です。

`using`キーワードで宣言した変数は、リソースタイプ変数として見なされ、変数がscope外になる前に自動でリソース解除作業を行うことができるようになり、従来の明示的にリソース解除コードを書くという面倒な作業がだいぶ楽になることが期待できます。

ECMAScript Proposalは以下となっておりStage3段階となってます。
https://github.com/tc39/proposal-explicit-resource-management#status
※ Stage4になると標準として確定されます。詳しくは[The TC39 Process](https://tc39.es/process-document/)をご参考ください。

また、TypeScript 5.2では`using`keywordが利用可能です。
※ただ、現在はpolyfillコードが必要だったりするようです。

この記事では、以下の内容を紹介します。

- 基本的な使い方
- 従来の課題と解決ポイント
- TypeScript 5.2での動作方法とコード例

# Sample Code
TypeScript 5.2での動作コード例は、以下のgithub repositoryにてご参考いただけます。
https://github.com/genie-oh/ts-explicit-resource-management

# 背景

ファイル操作、データベース操作、ネットワーク通信(Socket)など、外部リソースを扱う場合、リソースのopenとclose(expose & dispose)を明示的にハンドリングする必要があります。

**結論、上記のようなリソースの明示的なハンドリングは、プログラマーにおいて意外と負担がかかるという課題がありました。その課題を解決するために、`using` keywordが提案され、もうすぐ標準仕様として確定となる予定です。**

例えば、`node-postgres`を利用したPostgreSQLの操作において、`connect()`と`close()`を明示的に処理する必要があり、`close()`をしない場合はDB Connectionのリソース解除がされない状態からプロセスが終了しない問題などに起きたりします。

Javaの`AutoClosable`など、他の言語ではすでにこの問題のための機能が備わりつつありましたが、JavaScriptでは正式な機能はなかったのが現状でした。

そのため、プログラマーがいろいろ書き方を工夫して行なっていたのが従来の方法であり、以下のように`try-finally`で行うのがよくあるパターンと思います。

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

しかし、この方法でも、使うポイントごとに`try-finally`を書くとか、それを避けたく、Wrapper Classにて自動解除されるようなものを作ったりなど、いろいろ課題はありました。
一番大きいな問題は、チーム開発において、こちらを共通認識として一貫性を保ったコードの維持が、なかなか難しかったという課題を個人的には持っておりました。

# 基本的な構文

以下のように、`Symbol.dispose`をSymbol keyとして、リソース解除処理のcallback関数として明示的に定義し、`using` keywordでリソース変数を宣言して使う形になります。

`using`として宣言されたリソース変数に対しては、「呼び出すscope上での明示的にリソース解除」は不要となります。

リソース変数がscope外になる直前(callstack終了の直前)に、自動的に`Symbol.dispose`として定義していた関数が呼ばれ、リソース解除処理が行われるようになります。

```typescript
const getResource = () => {
  //... 利用したいリソースの初期化コードを作成
  return {
    //usingとして使われる変数は、Symbol.disposeというsymbol keyをもち、
    //resourceをdisposeする処理を行うcallback関数を持つ
    [Symbol.dispose]: () => {
      //... リソース解除ロジックを作成
    },
  };
};

function doWorkOnResource() {
  // using keywordを使いリソース変数を宣言
  using resource = getResource();
  //... some logic
  return;
  // using変数がスコープ外になると、自動的にdisposeが遂行される
}
```

async/awaitのように、非同期処理の動機化をしたい場合は、`Symbol.asyncDispose`を使う必要があります。

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

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

また、classで実装する場合は`Disposable`,`AsyncDisposable`というinterfaceをimplementsし実装することも可能です。

```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
  }
```

# ユースケース

ここでは、`node-postgres`を利用し、`PostgreSQL`にconnectしselectを行った後、closeを行う例を紹介します。

サンプルコードは、以下のgithub repositoryからご参考いただけます。
https://github.com/genie-oh/ts-explicit-resource-management

## pg-try-finally.example.ts

従来の`try-catch-finally`を使ったリソースハンドリングの例です。

```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 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

usingとasyncDisposeを使った例となります。
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 () => {
  // declear resource variable by `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 out of scope, resource will be disposed by function of [Symbol.asyncDispose]
};

isAvailableDBConnection();
```

## pg-asyncDispose-class.example.ts

usingとasyncDisposeを使った例となります。
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;
  }

  /**
   * implement by 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 () => {
  // declear resource variable by `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 out of scope, resource will be disposed by function of [Symbol.asyncDispose]
};

isAvailableDBConnection();
```

# 後書き

`using` keywordがもたらす一番のメリットは「標準化」と思います。
もちろん`using`キーワードが上記の問題を全てを解決してくれるわけではないと思いますが、かなり改善を望めると思います。

私の経験としては、チーム開発において「リソース管理のためのコード」が、メンバーによって差があったり、漏れていたりする問題で、ランタイム時にぶつかるややこしい問題に直面したことが多くあります。

最近は、そのためDI(Dependency Injection)が可能なframeworkを仕入れ、外部リソースをSingletonの生命周期、またはmethod levelの生命周期を意識して、必要に応じて共通化した後、ミドルウェアとして、チーム全体で意識を合わせたあと、開発をしており、今後もそうしようと思っておりました。
ただ、こちらもこちらで、初期開発や共通認識化において難しい点はあると感じます。
ORMをよく採用したい理由としても、他の理由もいろいろありますが、上記の理由も一つではあります。
また、外部リソースはDBだけではないため、外部リソース変数の管理は昔から常に課題でした。

そこで、`using`という標準機能が用意されることで、JavaScript, TypeScriptを扱うプログラマーを自然的にこの知識を身につけるようになることで、チーム開発においてのリソース管理における品質維持も楽になると期待しています。

# 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/)