Closed5

using: Explicit Resource Management feature in ECMAScript

sugar-catsugar-cat

https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-2.html#using-declarations-and-explicit-resource-management

そもそも何?

  • TypeScript 5.2では、ECMAScriptの新しいExplicit Resource Management(明示的リソース管理)機能がサポート

何ができる

  • オブジェクト作成後のクリーンアップ: Symbol.dispose
  • DisposableというGlobal Objectの追加

使い方

  • usingというキーワードとともに宣言された変数は、スコープの終わりにSymbol.disposeメソッドが自動的に呼び出される
    • try finallyを明示的に記述する必要がない
  • Symbol.asyncDisposeによって非同期の解放もサポート
  • DisposableStackAsyncDisposableStackによって複数リソースのクリーンアップも可能

注意点

  • ランタイムでのネイティブサポートがまだなのでコンパイルターゲットをes2022以下に設定、ライブラリ設定にesnextまたはesnext.disposableを含める必要がある
export function doSomeWork() {
	using file = new TempFile(".some_temp_file");
	if (someCondition()) {
		// do some more work...
		return;
	}
}

実行タイミング

  • スコープを抜ける時、リターン時、throw時
    • 基本的に実行順はFILO
function loggy(id: string): Disposable {
    console.log(`Creating ${id}`);
    return {
        [Symbol.dispose]() {
            console.log(`Disposing ${id}`);
        }
    }
}
function func() {
    using a = loggy("a");
    using b = loggy("b");
    {
        using c = loggy("c");
        using d = loggy("d");
    }
    using e = loggy("e");
    return;
    // Unreachable.
    // Never created, never disposed.
    using f = loggy("f");
}
func();
// Creating a
// Creating b
// Creating c
// Creating d
// Disposing d
// Disposing c
// Creating e
// Disposing e
// Disposing b
// Disposing a
  • 例外
    Symbol.disposeの処理内で例外がthrowされた場合は、ErrorのサブタイプのSuppressedErrorが投げられる
    最後にthrowされたエラーを保持するsuppressedプロパティと、最近throwされたエラーを保持するerrorプロパティを備えている。

  • 軽量に書く
    Disposableを実装したクラスを定義するのは一般的に認知負荷が高く、ライブラリ等の開発以外では抽象化しすぎかもしれない。
    なのでDisposableStackを使用した単発のクリーンアップを行うオブジェクトを使うと良い。

function doSomeWork() {
    const path = ".some_temp_file";
    const file = fs.openSync(path, "w+");
    using cleanup = new DisposableStack();
    cleanup.defer(() => {
        fs.closeSync(file);
        fs.unlinkSync(path);
    });
    // use file...
    if (someCondition()) {
        // do some more work...
        return;
    }
    // ...
}
  • ネイティブのランタイムはまだサポートが薄い(以下のポリフィルが必要)
- `Symbol.dispose`
- `Symbol.asyncDispose`
- `DisposableStack`
- `AsyncDisposableStack`
- `SuppressedError`

サポートされていなくても組み込みのシンボルをポリフィン具することで使用可能

Symbol.dispose ??= Symbol("Symbol.dispose");
Symbol.asyncDispose ??= Symbol("Symbol.asyncDispose");

またコンパイルターゲットの設定も忘れずに

{
    "compilerOptions": {
        "target": "es2022",
        "lib": ["es2022", "esnext.disposable", "dom"]
    }
}
sugar-catsugar-cat

https://www.typescriptlang.org/docs/handbook/symbols.html

  • そもそも何?
    一意の値を生成するために使用される特別なデータ型。オブジェクトのプロパティ名の衝突を避けることができる。

  • 何ができる

プロパティキーの衝突回避

let sym2 = Symbol("key");
let sym3 = Symbol("key");
sym2 === sym3; // false, symbols are unique	

カプセル化
Symbolプロパティはfor...inループやObject.keys()メソッドでは列挙されない

ウェルノウンシンボル
Symbol.iteratorSymbol.asyncIteratorなどの組み込みSymbolを使用してカスタマイズができる

class Range {
  constructor(start, end) {
    this.start = start;
    this.end = end;
  }

  [Symbol.iterator]() {
    let current = this.start;
    let end = this.end;

    // ジェネレータ関数を使用してイテレーションロジックを定義
    return {
      next() {
        if (current <= end) {
          return { value: current++, done: false };
        } else {
          return { done: true };
        }
      }
    };
  }
}

// Rangeオブジェクトを使用
let range = new Range(1, 3);

for (let num of range) {
  console.log(num);  // 1, 2, 3 を順に出力
}
sugar-catsugar-cat

各ランタイム上でのusing対応状況
Node.js: v20.4.0
https://nodejs.org/en/blog/release/v20.4.0#support-to-the-explicit-resource-management-proposal
Deno: v1.38
https://deno.com/blog/v1.38#using-with-deno-apis
Bun: v1.0.23
https://bun.sh/blog/bun-v1.0.23#resource-management-is-now-supported

Workers
V8 エンジン側でまだサポートされてないので、Wranglerのプレスリリース版を使用するしかない
https://developers.cloudflare.com/workers/runtime-apis/rpc/lifecycle/#how-to-use-the-using-declaration-in-your-worker

このスクラップは4日前にクローズされました