using

可用性有限

此特性不是基線特性,因為它在一些最廣泛使用的瀏覽器中不起作用。

using 宣告聲明瞭塊級作用域的區域性變數,這些變數是同步處置的。與 const 類似,用 using 宣告的變數必須被初始化且不能被重新賦值。變數的值必須是 nullundefined,或者是一個帶有 [Symbol.dispose]() 方法的物件。當變數超出作用域時,物件的 [Symbol.dispose]() 方法會被呼叫,以確保資源被釋放。

語法

js
using name1 = value1;
using name1 = value1, name2 = value2;
using name1 = value1, name2 = value2, /* …, */ nameN = valueN;
nameN

要宣告的變數名。每個變數名都必須是合法的 JavaScript 識別符號,並且不能解構繫結模式

valueN

變數的初始值。它可以是任何合法的表示式,但其值必須是 nullundefined,或者是一個帶有 [Symbol.dispose]() 方法的物件。

描述

此宣告可用於

最值得注意的是,它不能用於

  • 指令碼的頂層,因為指令碼作用域是持久的。
  • switch 語句的頂層。
  • for...in 迴圈的初始化器中。因為迴圈變數只能是字串或符號,所以這樣做沒有意義。

using 宣告一個一次性資源,該資源與變數作用域(塊、函式、模組等)的生命週期繫結。當作用域退出時,資源會同步被處置。變數可以為 nullundefined,因此資源可以是可選的。

當變數首次宣告且其值為非空(non-nullish)時,會從物件中獲取一個處置器。如果 [Symbol.dispose] 屬性不包含函式,則會丟擲 TypeError。此處置器會儲存到作用域中。

當變數超出作用域時,處置器會被呼叫。如果作用域包含多個 usingawait using 宣告,所有處置器將按宣告的逆序執行,無論宣告型別如何。所有處置器都保證會執行(很像 try...catch...finally 中的 finally 塊)。所有在處置過程中丟擲的錯誤,包括導致作用域退出的初始錯誤(如果適用),都會被聚合到一個 SuppressedError 中,其中較早的異常作為 suppressed 屬性,較晚的異常作為 error 屬性。此 SuppressedError 會在處置完成後丟擲。

using 將資源管理與詞法作用域繫結,這既方便有時也令人困惑。有許多方法可以在變數本身超出作用域時保留變數的值,因此你可能會持有對已處置資源的引用。有關可能不按預期行為的示例,請參閱下文。如果你想手動管理資源處置,同時保持相同的錯誤處理保證,可以使用 DisposableStack 代替。

示例

在以下示例中,我們假設有一個簡單的 Resource 類,它具有 getValue 方法和 [Symbol.dispose]() 方法

js
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 宣告的資源在退出塊時被處置。

js
{
  using resource = new Resource();
  console.log(resource.getValue());
  // resource disposed here
}

函式中的 using

你可以在函式體中使用 using。在這種情況下,資源在函式執行完畢時,緊接在函式返回之前被處置。

js
function example() {
  using resource = new Resource();
  return resource.getValue();
}

在這裡,resource[Symbol.dispose]() 將在 getValue() 之後,在 return 語句執行之前被呼叫。

在資源被 閉包 捕獲的情況下,它可能會比宣告存活更久

js
function example() {
  using resource = new Resource();
  return () => resource.getValue();
}

在這種情況下,如果你呼叫 example()(),你將始終在一個已經處置的資源上執行 getValue,因為資源在 example 返回時已被處置。如果你想在回撥被呼叫一次後立即處置資源,請考慮這種模式

js
function example() {
  const resource = new Resource();
  return () => {
    using resource2 = resource;
    return resource2.getValue();
  };
}

在這裡,我們將用 const 宣告的資源別名為一個用 using 宣告的資源,這樣資源只在回撥被呼叫後才會被處置;請注意,如果回撥從未被呼叫,則資源將永遠不會被清理。

模組中的 using

你可以在模組的頂層使用 using。在這種情況下,資源在模組執行完畢時被處置。

js
using resource = new Resource();
export const value = resource.getValue();
// resource disposed here

export using 是無效語法,但你可以 export 一個在其他地方用 using 宣告的變數

js
using resource = new Resource();
export { resource };

這仍然不被鼓勵,因為匯入者將始終收到已處置的資源。類似於閉包問題,這會導致資源的值比變數的生命週期更長。

for...ofusing

你可以在 for...of 迴圈的初始化器中使用 using。在這種情況下,資源在每次迴圈迭代時都會被處置。

js
const resources = [new Resource(), new Resource(), new Resource()];
for (using resource of resources) {
  console.log(resource.getValue());
  // resource disposed here
}

多個 using

以下是宣告多個可處置資源的兩種等效方式

js
using resource1 = new Resource(),
  resource2 = new Resource();

// OR

using resource1 = new Resource();
using resource2 = new Resource();

在這兩種情況下,當作用域退出時,resource2resource1 之前被處置。這是因為 resource2 可能依賴於 resource1,因此它首先被處置以確保在 resource2 被處置時 resource1 仍然可用。

可選的 using

using 允許變數為 nullundefined,因此資源可以是可選的。這意味著你不需要這樣做

js
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);
}

但可以這樣做

js
using resource = acquireResource();
console.log(resource?.getValue());

不使用變數的 using 宣告

你可以使用 using 實現自動資源處置,甚至不需要使用變數。這對於在塊中設定上下文非常有用,例如建立鎖

js
{
  using _ = new Lock();
  // Perform concurrent operations here
  // Lock disposed (released) here
}

請注意,_ 是一個普通識別符號,但它約定俗成地用作“一次性”變數。要建立多個未使用變數,你需要使用不同的名稱,例如使用以 _ 為字首的變數名。

初始化和暫時性死區

using 變數受制於與 letconst 變數相同的 暫時性死區 限制。這意味著你不能在初始化之前訪問變數——資源的有效生命週期嚴格地從其初始化到其作用域的結束。這使得 RAII 風格的資源管理成為可能。

js
let useResource;
{
  useResource = () => resource.getValue();
  useResource(); // Error: Cannot access 'resource' before initialization
  using resource = new Resource();
  useResource(); // Valid
}
useResource(); // Error: Resource is disposed

錯誤處理

using 宣告在存在錯誤的情況下管理資源處置最為有用。如果不小心,一些資源可能會因為錯誤阻止後續程式碼執行而洩漏。

js
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,你可能會這樣做

js
try {
  const resource = new Resource();
  handleResource(resource);
  resource[Symbol.dispose]();
} catch (e) {
  console.error(e);
}

但是,如果 handleResource() 丟擲錯誤,那麼控制流永遠不會到達 resource[Symbol.dispose](),並且資源會洩漏。此外,如果你有兩個資源,那麼在早期處置中丟擲的錯誤可能會阻止後期處置的執行,導致更多的洩漏。

考慮一個更復雜的情況,其中處置器本身丟擲錯誤

js
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

瀏覽器相容性

另見