だめ
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 的介紹。
這個 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:儲存非同步可丟棄資源的堆疊
這個 proposal 是為了解決以下這些情境。
不一致的資源管理模式。
迭代器:iterator.return()
串流讀取器:reader.releaseLock()
Node 的檔案 handle:handle.close()
Emscripten 的 C++ 物件 handle:Module._free(ptr)、obj.delete()、Module.destroy(obj)
避免管理資源時常見的踩雷點。
const reader = stream.getReader();
...
reader.releaseLock(); // 應該放進 finally
作用域。
const handle = ...;
try {
... // 這裡可以使用 handle
}
finally {
handle.close();
}
// 這裡已經不能使用 handle 了,但定義還留著
正確管理多個資源時,程式往往會變得冗長。
{ // 假設 b 依賴於 a
const a = ...;
try {
const b = ...;
try {
...
}
finally {
b.close(); // 先關閉 b
}
}
finally {
a.close(); // 要能在 b.close() 出錯時也正常執行
}
}
可以寫成這樣
using a = ..., b = ...;
...
非阻塞 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 ...;
... // 寫入鎖仍然持有中
} // 寫入鎖會被釋放
與 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 會自動解鎖
}
既有實作。
・C# 的 using
・Java 的 try-with-resources
・Python 的 with
同步綁定。
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 可以綁定多個資源。
{
...
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;
}
}
}
為了確保資源能正確釋放,即使初始化過程中發生異常,也必須能正常執行清理。
如果一次宣告多個資源,它們會依宣告順序初始化,並以相反順序釋放。
本 proposal 允許將 null、undefined 傳給 using,而且會直接忽略它們。
這和 C# 的 using 類似。
其中一個原因,是為了在資源是可選的情況下簡化寫法,避免多餘的語法和不必要的記憶體配置。
if (isResourceAvailable()) {
using resource = getResource();
... // (1)
resource.doSomething()
... // (2)
}
else {
// 這邊也得寫一份類似的東西
... // (1)
... // (2)
}
// 這樣就好了
using resource = isResourceAvailable() ? getResource() : undefined;
... // (1) 不管有沒有資源都會執行
resource?.doSomething();
... // (2) 有資源就執行
如果資源沒有 [Symbol.dispose],在嘗試追蹤它的當下就會發生 TypeError。
可以在 for-of 與 for-await-of 迴圈的變數宣告中寫 using。
for (using x of iterateResources()) {
// 使用 x 做某件事
}
每一輪迴圈綁定的值,都會在該輪結束時被釋放。
不過如果因為 return、break、throw 等離開迴圈,尚未成為迴圈對象的資源不會被釋放。
for-in 迴圈不能使用 using。
非同步綁定。
{
... // (1)
await using x = expr1;
... // (2)
}
await using 建立的綁定,會在其所屬的非同步函式或區塊結束時被追蹤並釋放。
以下是 await using 的定義等內容,但和不帶 await 的 using 幾乎相同,所以略過。
如果資源同時沒有 [Symbol.dispose] 與 [Symbol.asyncDispose],在嘗試追蹤它的當下就會發生 TypeError。
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。
以下是這個 proposal 被採用後的 API 使用範例。
{
using reader = stream.getReader();
const { value, done } = reader.read();
} // reader 會被釋放
{
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
{
await using writable = ...;
writable.write(...);
} // 會呼叫 writable.end(),並等待結果返回
// 稽核特權函式呼叫的進入與離開
function privilegedActivity() {
using activity = auditLog.startActivity("privilegedActivity"); // 開始 activity
...
} // activity 結束
import { Semaphore } from "...";
const sem = new Semaphore(1); // 同時只能有 1 人
export async function tryUpdate(record) {
using lck = await sem.wait(); // 等到只剩 1 人
...
} // Semaphore 會被釋放,並通知下一位等待者
// 只要任一邊失敗就回滾
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
Symbol 會新增 dispose 與 asyncDispose 屬性。
dispose 會在 using 結束時呼叫,asyncDispose 則會在 await using 結束時呼叫。
interface SymbolConstructor {
readonly asyncDispose: unique symbol;
readonly dispose: unique symbol;
}
這一段有內部結構與實作 dispose 時的資訊,不過真正會去實作的人應該不多,而且那種人多半會直接看原文,所以就省略了。
這樣就不會再因為一不小心忘記釋放資源而出事了。
太好了。
不過像 Node 的 crypto.Hash 的清理是靠 hash.digest,這並不是說在現版本的 Node 中,離開 using 區塊時就會自動呼叫它;完全不是這樣,而是要由 Node 端努力實作才行。
雖然已經從優先度高的地方 逐步在支援,但絕對不是現在就能對所有資源立刻使用。
而且如果不小心把尚未支援的資源拿去 using,還會報錯。
有點可惜呢。
順帶一提,如果是 PHP,只要沒有任何地方再引用資源,就會自動幫你釋放。
太好了。
原文出處:https://qiita.com/rana_kualu/items/83783c6dd3b4b51fa77f