using
Die using
-Deklaration deklariert block-skopierte lokale Variablen, die synchron entsorgt werden. Wie bei const
müssen Variablen, die mit using
deklariert werden, initialisiert werden und können nicht neu zugewiesen werden. Der Wert der Variablen muss entweder null
, undefined
oder ein Objekt mit einer [Symbol.dispose]()
-Methode sein. Wenn die Variable den Gültigkeitsbereich verlässt, wird die [Symbol.dispose]()
-Methode des Objekts aufgerufen, um sicherzustellen, dass Ressourcen freigegeben werden.
Syntax
using name1 = value1;
using name1 = value1, name2 = value2;
using name1 = value1, name2 = value2, /* …, */ nameN = valueN;
nameN
-
Der Name der zu deklarierenden Variable. Jede muss ein gültiger JavaScript-Bezeichner sein und kein Destructuring-Bindungsmuster.
valueN
-
Initialwert der Variable. Es kann jedem gültigen Ausdruck entsprechen, aber sein Wert muss entweder
null
,undefined
oder ein Objekt mit einer[Symbol.dispose]()
-Methode sein.
Beschreibung
Diese Deklaration kann verwendet werden:
- Innerhalb eines Blocks
- Innerhalb eines Funktionskörpers oder statischen Initialisierungsblocks einer Klasse
- Auf Modulebene eines Moduls
- In der Initialisierung einer
for
,for...of
oderfor await...of
-Schleife
Insbesondere kann es nicht verwendet werden:
- Auf oberster Ebene eines Skripts, da Skriptbereiche persistent sind.
- Auf oberster Ebene einer
switch
-Anweisung. - In der Initialisierung einer
for...in
-Schleife. Da die Schleifenvariable nur ein String oder Symbol sein kann, ist dies nicht sinnvoll.
Ein using
deklariert eine verfügbare Ressource, die an die Lebensdauer des Gültigkeitsbereichs der Variablen (Block, Funktion, Modul, etc.) gebunden ist. Wenn der Gültigkeitsbereich beendet wird, wird die Ressource synchron entsorgt. Die Variable darf den Wert null
oder undefined
haben, sodass die Ressource optional vorhanden sein kann.
Wenn die Variable zuerst deklariert wird und ihr Wert nicht null ist, wird ein Disposer aus dem Objekt abgerufen. Wenn die [Symbol.dispose]
-Eigenschaft keine Funktion enthält, wird ein TypeError
ausgelöst. Dieser Disposer wird im Gültigkeitsbereich gespeichert.
Wenn die Variable aus dem Gültigkeitsbereich verschwindet, wird der Disposer aufgerufen. Wenn der Gültigkeitsbereich mehrere using
- oder await using
-Deklarationen enthält, werden alle Disposer in umgekehrter Reihenfolge der Deklaration ausgeführt, unabhängig vom Deklarationstyp. Es wird garantiert, dass alle Disposer ausgeführt werden (ähnlich wie der finally
-Block in try...catch...finally
). Alle während der Entsorgung ausgelösten Fehler, einschließlich des ursprünglichen Fehlers, der den Gültigkeitsbereich verlassen hat (falls zutreffend), werden alle in einem SuppressedError
aggregiert, wobei jeder frühere Ausnahmefall als die „suppressed“-Eigenschaft und der spätere als die „error“-Eigenschaft hinzugefügt wird. Dieser SuppressedError
wird ausgelöst, nachdem die Entsorgung abgeschlossen ist.
using
verbindet das Ressourcenmanagement mit lexikalischen Gültigkeitsbereichen, was sowohl praktisch als auch manchmal verwirrend ist. Es gibt viele Möglichkeiten, den Wert der Variablen beizubehalten, auch wenn die Variable selbst außerhalb des Gültigkeitsbereichs ist, sodass Sie möglicherweise eine Referenz auf eine bereits entsorgte Ressource halten. Im Folgenden finden Sie einige Beispiele, in denen es möglicherweise nicht wie erwartet funktioniert. Wenn Sie das Ressourcenmanagement manuell verwalten möchten, während Sie dieselben Fehlermanagementgarantien beibehalten, können Sie stattdessen DisposableStack
verwenden.
Beispiele
In den folgenden Beispielen nehmen wir eine einfache Resource
-Klasse an, die eine getValue
-Methode und eine [Symbol.dispose]()
-Methode hat:
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
in einem Block
Die mit using
deklarierte Ressource wird beim Verlassen des Blocks entsorgt.
{
using resource = new Resource();
console.log(resource.getValue());
// resource disposed here
}
using
in einer Funktion
Sie können using
im Funktionskörper verwenden. In diesem Fall wird die Ressource entsorgt, wenn die Funktion die Ausführung beendet, unmittelbar bevor die Funktion zurückkehrt.
function example() {
using resource = new Resource();
return resource.getValue();
}
Hier wird resource[Symbol.dispose]()
nach getValue()
aufgerufen, bevor die return
-Anweisung ausgeführt wird.
Die Ressource kann die Deklaration überleben, falls sie von einer Closure erfasst wird:
function example() {
using resource = new Resource();
return () => resource.getValue();
}
In diesem Fall, wenn Sie example()()
aufrufen, werden Sie immer getValue
auf einer bereits entsorgten Ressource ausführen, da die Ressource entsorgt wurde, als example
zurückkehrt. Wenn Sie die Ressource unmittelbar nach dem einmaligen Aufruf des Rückrufs entsorgen möchten, sollten Sie dieses Muster betrachten:
function example() {
const resource = new Resource();
return () => {
using resource2 = resource;
return resource2.getValue();
};
}
Hier aliasen wir eine mit const
deklarierte Ressource zu einer mit using
deklarierten Ressource, sodass die Ressource erst entsorgt wird, nachdem der Rückruf aufgerufen wurde; beachten Sie, dass sie nie aufgeräumt wird, wenn sie nie aufgerufen wird.
using
in einem Modul
Sie können using
auf oberster Ebene eines Moduls verwenden. In diesem Fall wird die Ressource entsorgt, wenn das Modul die Ausführung beendet.
using resource = new Resource();
export const value = resource.getValue();
// resource disposed here
export using
ist ungültige Syntax, aber Sie können eine anderswo mit using
deklarierte Variable exportieren:
using resource = new Resource();
export { resource };
Dies ist dennoch nicht empfehlenswert, da der Importeur immer eine entsorgte Ressource erhält. Ähnlich wie beim Closure-Problem führt dies dazu, dass der Wert der Ressource länger lebt als die Variable.
using
mit for...of
Sie können using
in der Initialisierung einer for...of
-Schleife verwenden. In diesem Fall wird die Ressource bei jeder Schleifeniteration entsorgt.
const resources = [new Resource(), new Resource(), new Resource()];
for (using resource of resources) {
console.log(resource.getValue());
// resource disposed here
}
Mehrere using
Die folgenden sind zwei gleichwertige Möglichkeiten, mehrere verfügbare Ressourcen zu deklarieren:
using resource1 = new Resource(),
resource2 = new Resource();
// OR
using resource1 = new Resource();
using resource2 = new Resource();
In beiden Fällen wird bei Beendigung des Gültigkeitsbereichs resource2
vor resource1
entsorgt. Dies liegt daran, dass resource2
eine Abhängigkeit von resource1
haben könnte, daher wird es zuerst entsorgt, um sicherzustellen, dass resource1
noch verfügbar ist, wenn resource2
entsorgt wird.
Optionales using
using
erlaubt der Variable, den Wert null
oder undefined
zu haben, sodass die Ressource optional vorhanden sein kann. Das bedeutet, dass Sie nicht das Folgende tun müssen:
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);
}
Sondern dies tun können:
using resource = acquireResource();
console.log(resource?.getValue());
using
-Deklaration ohne Verwendung der Variablen
Sie können eine automatische Ressourcendisponierung mit using
erreichen, ohne die Variable tatsächlich zu verwenden. Dies ist sehr nützlich, um einen Kontext innerhalb eines Blocks einzurichten, wie zum Beispiel das Erstellen eines Locks:
{
using _ = new Lock();
// Perform concurrent operations here
// Lock disposed (released) here
}
Beachten Sie, dass _
ein normaler Bezeichner ist, es ist jedoch eine Konvention, ihn als "Wegwerf"-Variable zu verwenden. Um mehrere ungenutzte Variablen zu erstellen, müssen Sie unterschiedliche Namen verwenden, beispielsweise durch Verwendung eines mit _
vorgestellten Variablennamens.
Initialisierung und temporäre tote Zonen
using
-Variablen unterliegen denselben temporären toten Zonen-Einschränkungen wie let
- und const
-Variablen. Das bedeutet, dass Sie nicht auf die Variable zugreifen können, bevor die Initialisierung erfolgt ist—die gültige Lebensdauer der Ressource reicht streng von ihrer Initialisierung bis zum Ende ihres Gültigkeitsbereichs. Dies ermöglicht RAII-basiertes Ressourcenmanagement.
let useResource;
{
useResource = () => resource.getValue();
useResource(); // Error: Cannot access 'resource' before initialization
using resource = new Resource();
useResource(); // Valid
}
useResource(); // Error: Resource is disposed
Fehlerbehandlung
Die using
-Deklaration ist am nützlichsten für das Ressourcenmanagement unter Berücksichtigung von Fehlern. Wenn Sie nicht aufpassen, können einige Ressourcen verloren gehen, weil der Fehler verhindert, dass nachfolgender Code ausgeführt wird.
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);
}
Dies wird den von handleResource
geworfenen Fehler erfolgreich abfangen und protokollieren, und unabhängig davon, ob handleResource
einen Fehler auslöst oder nicht, wird die Ressource vor dem Verlassen des try
-Blocks entsorgt.
Hier, wenn Sie using
nicht verwenden, könnten Sie etwas tun wie:
try {
const resource = new Resource();
handleResource(resource);
resource[Symbol.dispose]();
} catch (e) {
console.error(e);
}
Aber wenn handleResource()
einen Fehler auslöst, erreicht die Kontrolle niemals resource[Symbol.dispose]()
, und die Ressource geht verloren. Darüber hinaus können bei zwei Ressourcen Fehler, die bei früheren Entsorgungen geworfen werden, verhindern, dass spätere Entsorgungen ausgeführt werden, was zu weiteren Verlusten führt.
Betrachten Sie einen komplizierteren Fall, bei dem der Disposer selbst einen Fehler auslöst:
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;
}
Sie können den Fehler, der in der Konsole Ihres Browsers geworfen wurde, inspizieren. Er hat die folgende Struktur:
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
Wie Sie sehen können, enthält error
alle Fehler, die während der Entsorgung geworfen wurden, als SuppressedError
. Jeder zusätzliche Fehler wird als error
-Eigenschaft hinzugefügt, und der ursprüngliche Fehler wird als suppressed
-Eigenschaft hinzugefügt.
Spezifikationen
Specification |
---|
ECMAScript Async Explicit Resource Management # prod-UsingDeclaration |