using
using 宣告聲明瞭塊級作用域的區域性變數,這些變數是同步處置的。與 const 類似,用 using 宣告的變數必須被初始化且不能被重新賦值。變數的值必須是 null、undefined,或者是一個帶有 [Symbol.dispose]() 方法的物件。當變數超出作用域時,物件的 [Symbol.dispose]() 方法會被呼叫,以確保資源被釋放。
語法
using name1 = value1;
using name1 = value1, name2 = value2;
using name1 = value1, name2 = value2, /* …, */ nameN = valueN;
描述
此宣告可用於
最值得注意的是,它不能用於
using 宣告一個一次性資源,該資源與變數作用域(塊、函式、模組等)的生命週期繫結。當作用域退出時,資源會同步被處置。變數可以為 null 或 undefined,因此資源可以是可選的。
當變數首次宣告且其值為非空(non-nullish)時,會從物件中獲取一個處置器。如果 [Symbol.dispose] 屬性不包含函式,則會丟擲 TypeError。此處置器會儲存到作用域中。
當變數超出作用域時,處置器會被呼叫。如果作用域包含多個 using 或 await using 宣告,所有處置器將按宣告的逆序執行,無論宣告型別如何。所有處置器都保證會執行(很像 try...catch...finally 中的 finally 塊)。所有在處置過程中丟擲的錯誤,包括導致作用域退出的初始錯誤(如果適用),都會被聚合到一個 SuppressedError 中,其中較早的異常作為 suppressed 屬性,較晚的異常作為 error 屬性。此 SuppressedError 會在處置完成後丟擲。
using 將資源管理與詞法作用域繫結,這既方便有時也令人困惑。有許多方法可以在變數本身超出作用域時保留變數的值,因此你可能會持有對已處置資源的引用。有關可能不按預期行為的示例,請參閱下文。如果你想手動管理資源處置,同時保持相同的錯誤處理保證,可以使用 DisposableStack 代替。
示例
在以下示例中,我們假設有一個簡單的 Resource 類,它具有 getValue 方法和 [Symbol.dispose]() 方法
class Resource {
value = Math.random();
#isDisposed = false;
getValue() {
if (this.#isDisposed) {
throw new Error("Resource is disposed");
}
return this.value;
}
[Symbol.dispose]() {
this.#isDisposed = true;
console.log("Resource disposed");
}
}
塊中的 using
用 using 宣告的資源在退出塊時被處置。
{
using resource = new Resource();
console.log(resource.getValue());
// resource disposed here
}
函式中的 using
你可以在函式體中使用 using。在這種情況下,資源在函式執行完畢時,緊接在函式返回之前被處置。
function example() {
using resource = new Resource();
return resource.getValue();
}
在這裡,resource[Symbol.dispose]() 將在 getValue() 之後,在 return 語句執行之前被呼叫。
在資源被 閉包 捕獲的情況下,它可能會比宣告存活更久
function example() {
using resource = new Resource();
return () => resource.getValue();
}
在這種情況下,如果你呼叫 example()(),你將始終在一個已經處置的資源上執行 getValue,因為資源在 example 返回時已被處置。如果你想在回撥被呼叫一次後立即處置資源,請考慮這種模式
function example() {
const resource = new Resource();
return () => {
using resource2 = resource;
return resource2.getValue();
};
}
在這裡,我們將用 const 宣告的資源別名為一個用 using 宣告的資源,這樣資源只在回撥被呼叫後才會被處置;請注意,如果回撥從未被呼叫,則資源將永遠不會被清理。
模組中的 using
你可以在模組的頂層使用 using。在這種情況下,資源在模組執行完畢時被處置。
using resource = new Resource();
export const value = resource.getValue();
// resource disposed here
export using 是無效語法,但你可以 export 一個在其他地方用 using 宣告的變數
using resource = new Resource();
export { resource };
這仍然不被鼓勵,因為匯入者將始終收到已處置的資源。類似於閉包問題,這會導致資源的值比變數的生命週期更長。
帶 for...of 的 using
你可以在 for...of 迴圈的初始化器中使用 using。在這種情況下,資源在每次迴圈迭代時都會被處置。
const resources = [new Resource(), new Resource(), new Resource()];
for (using resource of resources) {
console.log(resource.getValue());
// resource disposed here
}
多個 using
以下是宣告多個可處置資源的兩種等效方式
using resource1 = new Resource(),
resource2 = new Resource();
// OR
using resource1 = new Resource();
using resource2 = new Resource();
在這兩種情況下,當作用域退出時,resource2 在 resource1 之前被處置。這是因為 resource2 可能依賴於 resource1,因此它首先被處置以確保在 resource2 被處置時 resource1 仍然可用。
可選的 using
using 允許變數為 null 或 undefined,因此資源可以是可選的。這意味著你不需要這樣做
function acquireResource() {
// Imagine some real-world relevant condition here,
// such as whether there's space to allocate for this resource
if (Math.random() < 0.5) {
return null;
}
return new Resource();
}
const maybeResource = acquireResource();
if (maybeResource) {
using resource = maybeResource;
console.log(resource.getValue());
} else {
console.log(undefined);
}
但可以這樣做
using resource = acquireResource();
console.log(resource?.getValue());
不使用變數的 using 宣告
你可以使用 using 實現自動資源處置,甚至不需要使用變數。這對於在塊中設定上下文非常有用,例如建立鎖
{
using _ = new Lock();
// Perform concurrent operations here
// Lock disposed (released) here
}
請注意,_ 是一個普通識別符號,但它約定俗成地用作“一次性”變數。要建立多個未使用變數,你需要使用不同的名稱,例如使用以 _ 為字首的變數名。
初始化和暫時性死區
using 變數受制於與 let 和 const 變數相同的 暫時性死區 限制。這意味著你不能在初始化之前訪問變數——資源的有效生命週期嚴格地從其初始化到其作用域的結束。這使得 RAII 風格的資源管理成為可能。
let useResource;
{
useResource = () => resource.getValue();
useResource(); // Error: Cannot access 'resource' before initialization
using resource = new Resource();
useResource(); // Valid
}
useResource(); // Error: Resource is disposed
錯誤處理
using 宣告在存在錯誤的情況下管理資源處置最為有用。如果不小心,一些資源可能會因為錯誤阻止後續程式碼執行而洩漏。
function handleResource(resource) {
if (resource.getValue() > 0.5) {
throw new Error("Resource value too high");
}
}
try {
using resource = new Resource();
handleResource(resource);
} catch (e) {
console.error(e);
}
這將成功捕獲 handleResource 丟擲的錯誤並記錄它,無論 handleResource 是否丟擲錯誤,資源都會在退出 try 塊之前被處置。
在這裡,如果你不使用 using,你可能會這樣做
try {
const resource = new Resource();
handleResource(resource);
resource[Symbol.dispose]();
} catch (e) {
console.error(e);
}
但是,如果 handleResource() 丟擲錯誤,那麼控制流永遠不會到達 resource[Symbol.dispose](),並且資源會洩漏。此外,如果你有兩個資源,那麼在早期處置中丟擲的錯誤可能會阻止後期處置的執行,導致更多的洩漏。
考慮一個更復雜的情況,其中處置器本身丟擲錯誤
class CantDisposeMe {
#name;
constructor(name) {
this.#name = name;
}
[Symbol.dispose]() {
throw new Error(`Can't dispose ${this.#name}`);
}
}
let error;
try {
using resource1 = new CantDisposeMe("resource1");
using resource2 = new CantDisposeMe("resource2");
throw new Error("Error in main block");
} catch (e) {
error = e;
}
你可以在瀏覽器的控制檯中檢查丟擲的錯誤。它具有以下結構
SuppressedError: An error was suppressed during disposal
suppressed: SuppressedError: An error was suppressed during disposal
suppressed: Error: Can't dispose resource1
error: Error: Error in main block
error: Error: Can't dispose resource2
如你所見,error 包含所有在處置過程中丟擲的錯誤,作為一個 SuppressedError。每個附加錯誤都作為 error 屬性新增,原始錯誤作為 suppressed 屬性新增。
規範
| 規範 |
|---|
| ECMAScript 非同步顯式資源管理 # prod-UsingDeclaration |
瀏覽器相容性
載入中…