だめ

function hoge(){
  const handle = exampleResource(); // 某個資源
  /*
    某個很長的處理
  */
  handle.release(); // 釋放資源
}

hoge();

如果不小心在長時間處理途中 return,或是發生例外,資源就會在沒有被釋放的情況下殘留。
因此,用 try-finally 包起來的寫法就成了定石。

OK 但很麻煩

function hoge(){
  const handle = exampleResource();
  try{
    /*
      某個很長的處理
    */
  }finally{
    handle.release(); // 釋放資源
  }
}

hoge();

嗯,真的很麻煩呢。

這還只是單一資源,算是還好;但如果還要把相關的多個資源按順序排列之類,就會變成非常複雜的程式碼。
明明只是想單純釋放資源而已。

於是,為了更方便地管理資源,導入了 using 關鍵字。

很簡單!

function hoge(){
  using handle = exampleResource();
  /*
    某個很長的處理
  */
}

hoge();

離開作用域時,資源會自動被釋放。
這就是和 C# 等語言的 using 完全相同的功能。

這項功能在 2026/05 變成了 Stage 4,也就是說,已經有多個瀏覽器實作了。

Firefox 已在 2025/07/22 發布的 Firefox 141 支援,Chrome 也已在 2025/03/04 發布的 Chrome 134 支援。
而且 Safari 也已在 2026/08/13 的預覽版 TP 250 支援,所以過陣子正式版應該也會跟進。
可喜可賀,之後就能在所有瀏覽器中使用了。

以下是對應的 proposal,ECMAScript Explicit Resource Management 的介紹。

ECMAScript Explicit Resource Management

這個 proposal 的目的,是改善對記憶體、I/O 等各種資源生命週期的通用模式。
也就是資源的配置與釋放機制。

例如,產生器函式在呼叫 return 時,會執行 finally 區塊中的清理。

同步產生器

function * g() {
  const handle = acquireFileHandle(); // 重要資源
  try {
    ...
  }
  finally {
    handle.release(); // 釋放資源
  }
}

const obj = g();
try {
  const r = obj.next();
  ...
}
finally {
  obj.return(); // 會呼叫 finally
}

非同步產生器

async function * g() {
  const handle = acquireStream(); // 重要資源
  try {
    ...
  }
  finally {
    await stream.close(); // 釋放資源
  }
}

const obj = g();
try {
  const r = await obj.next();
  ...
}
finally {
  await obj.return(); // 會呼叫 finally
}

本提案為了簡化這種通用模式,導入了新的語法。

同步產生器

function * g() {
  using handle = acquireFileHandle(); // 區塊作用域
} // handle 會被清理

{
  using obj = g(); // 區塊作用域
  const r = obj.next();
} // 會呼叫 finally

非同步產生器

async function * g() {
  using handle = acquireFileHandle(); // 區塊作用域
} // handle 會被清理

{
  await using obj = g(); // 區塊作用域
  const r = await obj.next();
} // 會呼叫 finally

此外,為了管理多個資源狀態,還會新增兩個容器物件。
・DisposableStack:儲存可丟棄資源的堆疊
・AsyncDisposableStack:儲存非同步可丟棄資源的堆疊

Motivations

這個 proposal 是為了解決以下這些情境。

Inconsistent patterns for resource management

不一致的資源管理模式。

迭代器:iterator.return()
串流讀取器:reader.releaseLock()
Node 的檔案 handle:handle.close()
Emscripten 的 C++ 物件 handle:Module._free(ptr)obj.delete()Module.destroy(obj)

Avoiding common footguns when managing resources

避免管理資源時常見的踩雷點。

const reader = stream.getReader();
...
reader.releaseLock(); // 應該放進 finally

Scoping resources

作用域。

const handle = ...;
try {
  ... // 這裡可以使用 handle
}
finally {
  handle.close();
}
// 這裡已經不能使用 handle 了,但定義還留著

Avoiding lengthy code when managing multiple resources correctly

正確管理多個資源時,程式往往會變得冗長。

{ // 假設 b 依賴於 a
  const a = ...;
  try {
    const b = ...;
    try {
      ...
    }
    finally {
      b.close(); // 先關閉 b
    }
  }
  finally {
    a.close(); // 要能在 b.close() 出錯時也正常執行
  }
}

可以寫成這樣

using a = ..., b = ...;
...

Non-blocking memory/IO applications

非阻塞 I/O 應用程式。

import { ReaderWriterLock } from "...";
const lock = new ReaderWriterLock();

export async function readData() {
  // 等待寫入完成,取得讀取鎖
  using lockHandle = await lock.read();
  ... 
  await ...;
  ... // 讀取鎖仍然持有中
} // 讀取鎖會被釋放

export async function writeData(data) {
  // 等待讀取完成,取得寫入鎖
  using lockHandle = await lock.write();
  ... 
  await ...;
  ... // 寫入鎖仍然持有中
} // 寫入鎖會被釋放

Potential for use with the Fixed Layout Objects Proposal and shared struct

Structs proposal 的協同效果。

main.js

// main.js
shared struct class SharedData {
  ready = false;
  processed = false;
}

const worker = new Worker('worker.js');
const m = new Atomics.Mutex();
const cv = new Atomics.ConditionVariable();
const data = new SharedData();
worker.postMessage({ m, cv, data });

// 傳送給 worker
{
  // 等待取得鎖 m
  using lck = m.lock();

  data.ready = true;
  console.log("main is ready");

} // m 會自動解鎖

// 通知等待中的 worker
cv.notifyOne();

{
  // 再次取得鎖 m
  using lck = m.lock();

  // 解鎖 m,並等待 worker 完成
  cv.wait(m, () => data.processed);

} // m 會自動解鎖

worker.js

onmessage = function (e) {
  const { m, cv, data } = e.data;

  {
    // 等待取得鎖 m
    using lck = m.lock();

    // 解鎖 m,並等待資料到達
    cv.wait(m, () => data.ready);

    // 已取得鎖 m
    console.log("worker thread is processing data");

    // 傳送資料給 main
    data.processed = true;
    console.log("worker thread is done");

  } // m 會自動解鎖
}

Prior Art

既有實作。

・C# 的 using
・Java 的 try-with-resources
・Python 的 with

Semantics

using Declarations

同步綁定。

UsingDeclaration :
  `using` BindingList `;`

LexicalBinding :
    BindingIdentifier Initializer

解析器在找到 using 時,會將該綁定標記為在離開區塊時銷毀。
因此 using 不能宣告在 script 的最上層。

{
  ... // (1)
  using x = expr1;
  ... // (2)
}

上述具有以下執行期語意。

{
  const $$try = { stack: [], error: undefined, hasError: false };
  try {
    ... // (1)

    const x = expr1;
    if (x !== null && x !== undefined) {
      const $$dispose = x[Symbol.dispose];
      if (typeof $$dispose !== "function") {
        throw new TypeError();
      }
      $$try.stack.push({ value: x, dispose: $$dispose });
    }

    ... // (2)
  }
  catch ($$error) {
    $$try.error = $$error;
    $$try.hasError = true;
  }
  finally {
    while ($$try.stack.length) {
      const { value: $$expr, dispose: $$dispose } = $$try.stack.pop();
      try {
        $$dispose.call($$expr);
      }
      catch ($$error) {
        $$try.error = $$try.hasError ? new SuppressedError($$error, $$try.error) : $$error;
        $$try.hasError = true;
      }
    }
    if ($$try.hasError) {
      throw $$try.error;
    }
  }
}

using Declarations with Multiple Resources

一個 using 可以綁定多個資源。

{
  ...
  using x = expr1, y = expr2;
  ...
}

離開區塊或模組時,資源會被釋放,而且呼叫順序會與宣告順序相反。
幾乎等同於以下程式碼。

{
  ... // (1)
  using x = expr1;
  using y = expr2;
  ... // (2)
}

上述具有以下執行期語意。

{
  const $$try = { stack: [], error: undefined, hasError: false };
  try {
    ... // (1)

    const x = expr1;
    if (x !== null && x !== undefined) {
      const $$dispose = x[Symbol.dispose];
      if (typeof $$dispose !== "function") {
        throw new TypeError();
      }
      $$try.stack.push({ value: x, dispose: $$dispose });
    }

    const y = expr2;
    if (y !== null && y !== undefined) {
      const $$dispose = y[Symbol.dispose];
      if (typeof $$dispose !== "function") {
        throw new TypeError();
      }
      $$try.stack.push({ value: y, dispose: $$dispose });
    }

    ... // (2)
  }
  catch ($$error) {
    $$try.error = $$error;
    $$try.hasError = true;
  }
  finally {
    while ($$try.stack.length) {
      const { value: $$expr, dispose: $$dispose } = $$try.stack.pop();
      try {
        $$dispose.call($$expr);
      }
      catch ($$error) {
        $$try.error = $$try.hasError ? new SuppressedError($$error, $$try.error) : $$error;
        $$try.hasError = true;
      }
    }
    if ($$try.hasError) {
      throw $$try.error;
    }
  }
}

為了確保資源能正確釋放,即使初始化過程中發生異常,也必須能正常執行清理。
如果一次宣告多個資源,它們會依宣告順序初始化,並以相反順序釋放。

using Declarations and null or undefined Values

本 proposal 允許將 nullundefined 傳給 using,而且會直接忽略它們。
這和 C# 的 using 類似。
其中一個原因,是為了在資源是可選的情況下簡化寫法,避免多餘的語法和不必要的記憶體配置。

if (isResourceAvailable()) {
  using resource = getResource();
  ... // (1)
  resource.doSomething()
  ... // (2)
}
else {
  // 這邊也得寫一份類似的東西
  ... // (1) 
  ... // (2) 
}

// 這樣就好了
using resource = isResourceAvailable() ? getResource() : undefined;
... // (1) 不管有沒有資源都會執行
resource?.doSomething();
... // (2) 有資源就執行

using Declarations and Values Without [Symbol.dispose]

如果資源沒有 [Symbol.dispose],在嘗試追蹤它的當下就會發生 TypeError

using Declarations in for-of and for-await-of Loops

可以在 for-offor-await-of 迴圈的變數宣告中寫 using

for (using x of iterateResources()) {
  // 使用 x 做某件事
}

每一輪迴圈綁定的值,都會在該輪結束時被釋放。
不過如果因為 returnbreakthrow 等離開迴圈,尚未成為迴圈對象的資源不會被釋放。

for-in 迴圈不能使用 using

await using Declarations with Explicit Local Bindings

非同步綁定。

{
  ... // (1)
  await using x = expr1;
  ... // (2)
}

await using 建立的綁定,會在其所屬的非同步函式或區塊結束時被追蹤並釋放。

以下是 await using 的定義等內容,但和不帶 awaitusing 幾乎相同,所以略過。

await using Declarations and Values Without [Symbol.asyncDispose] or [Symbol.dispose]

如果資源同時沒有 [Symbol.dispose][Symbol.asyncDispose],在嘗試追蹤它的當下就會發生 TypeError

Implicit Async Interleaving Points ("implicit await")

await using 會在離開包含該宣告的非同步函式或區塊時,引入隱式的非同步交錯點。
也就是說,會隱式執行 await

async function f() {
  {
    a();
  } // 離開區塊
  b(); // 與 a 在同一個 microtask
}

目前直接這樣寫時,b() 會在和 a() 相同的 microtask 中執行;但一旦導入 await using,就會變成不同的 task。

async function f() {
  {
    await using x = ...;
    a();
  } // 離開區塊
  b(); // 與 a 不同的 microtask
}

關於 await using 語法如何與隱式的非同步交錯互動,請參見 https://github.com/tc39/proposal-async-explicit-resource-management/issues/1

Example

以下是這個 proposal 被採用後的 API 使用範例。

WHATWG Streams API

{
  using reader = stream.getReader();
  const { value, done } = reader.read();
} // reader 會被釋放

NodeJS FileHandle

{
  using f1 = await fs.promises.open(s1, constants.O_RDONLY),
        f2 = await fs.promises.open(s2, constants.O_WRONLY);
  const buffer = Buffer.alloc(4092);
  const { bytesRead } = await f1.read(buffer);
  await f2.write(buffer, 0, bytesRead);
} // 先釋放 f2,再釋放 f1

NodeJS Streams

{
  await using writable = ...;
  writable.write(...);
} // 會呼叫 writable.end(),並等待結果返回

Logging and tracing

// 稽核特權函式呼叫的進入與離開
function privilegedActivity() {
  using activity = auditLog.startActivity("privilegedActivity"); // 開始 activity
  ...
} // activity 結束

Async Coordination

import { Semaphore } from "...";
const sem = new Semaphore(1); // 同時只能有 1 人

export async function tryUpdate(record) {
  using lck = await sem.wait(); // 等到只剩 1 人
  ...
} // Semaphore 會被釋放,並通知下一位等待者

Three-Phase Commit Transactions

// 只要任一邊失敗就回滾
async function transfer(account1, account2) {
  await using tx = transactionManager.startTransaction(account1, account2);
  await account1.debit(amount);
  await account2.credit(amount);

  // 到這裡就表示成功
  tx.succeeded = true;
} // 等待 commit 或 rollback

Additions to Symbol

Symbol 會新增 disposeasyncDispose 屬性。
dispose 會在 using 結束時呼叫,asyncDispose 則會在 await using 結束時呼叫。

interface SymbolConstructor {
  readonly asyncDispose: unique symbol;
  readonly dispose: unique symbol;
}

Built-in Disposables

這一段有內部結構與實作 dispose 時的資訊,不過真正會去實作的人應該不多,而且那種人多半會直接看原文,所以就省略了。

感想

這樣就不會再因為一不小心忘記釋放資源而出事了。
太好了。

不過像 Node 的 crypto.Hash 的清理是靠 hash.digest,這並不是說在現版本的 Node 中,離開 using 區塊時就會自動呼叫它;完全不是這樣,而是要由 Node 端努力實作才行。
雖然已經從優先度高的地方 逐步在支援,但絕對不是現在就能對所有資源立刻使用。
而且如果不小心把尚未支援的資源拿去 using,還會報錯。
有點可惜呢。

順帶一提,如果是 PHP,只要沒有任何地方再引用資源,就會自動幫你釋放。
太好了。


原文出處:https://qiita.com/rana_kualu/items/83783c6dd3b4b51fa77f


精選技術文章翻譯,幫助開發者持續吸收新知。

共有 0 則留言


精選技術文章翻譯,幫助開發者持續吸收新知。
🏆 本月排行榜
🥇
站長阿川
📝16   💬4  
235
🥈
我愛JS
3
評分標準:發文×10 + 留言×3 + 獲讚×5 + 點讚×1 + 瀏覽數÷10
本數據每小時更新一次
📢 贊助商廣告 · 我要刊登