Dieser Inhalt wurde automatisch aus dem Englischen übersetzt, und kann Fehler enthalten. Erfahre mehr über dieses Experiment.

View in English Always switch to English

await using

Eingeschränkt verfügbar

Diese Funktion ist nicht Baseline, da sie in einigen der am weitesten verbreiteten Browser nicht funktioniert.

Want more browser support for this feature? Tell us why.

Die await using-Deklaration deklariert lokale Variablen mit Block-Gültigkeitsbereich, deren Ressourcen asynchron freigegeben werden. Wie bei const müssen mit await using deklarierte Variablen initialisiert werden und dürfen keinen neuen Wert zugewiesen bekommen. Der Wert einer solchen Variable muss entweder null, undefined oder ein Objekt mit einer [Symbol.asyncDispose]()- oder [Symbol.dispose]()-Methode sein. Wenn die Variable ihren Gültigkeitsbereich verlässt, wird die [Symbol.asyncDispose]()- oder [Symbol.dispose]()-Methode des Objekts aufgerufen und auf ihr Ergebnis gewartet, um sicherzustellen, dass Ressourcen freigegeben werden.

Syntax

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

Der Name der zu deklarierenden Variable. Jeder Name muss ein gültiger JavaScript-Bezeichner sein und darf kein Destrukturierungs-Bindungsmuster sein.

valueN

Anfangswert der Variable. Er kann ein beliebiger gültiger Ausdruck sein, sein Wert muss jedoch entweder null, undefined oder ein Objekt mit einer [Symbol.asyncDispose]()- oder [Symbol.dispose]()-Methode sein.

Beschreibung

Diese Deklaration kann nur dort verwendet werden, wo sowohl await als auch using zulässig sind. Dazu gehören:

  • Innerhalb eines Blocks (sofern sich der Block ebenfalls in einem asynchronen Kontext befindet)
  • Innerhalb des Rumpfs einer asynchronen Funktion oder einer asynchronen Generatorfunktion
  • Auf der obersten Ebene eines Moduls
  • Im Initialisierer einer for-, for...of- (sofern sich die for-Schleife ebenfalls in einem asynchronen Kontext befindet) oder for await...of-Schleife

Eine await using-Deklaration deklariert eine asynchron freizugebende Ressource, deren Lebensdauer an den Gültigkeitsbereich der Variable gebunden ist (Block, Funktion, Modul usw.). Beim Verlassen des Gültigkeitsbereichs wird die Ressource asynchron freigegeben. Die Syntax kann etwas verwirrend sein, da await nicht bei der ersten Deklaration der Variable eine Warteoperation auslöst, sondern erst, wenn die Variable ihren Gültigkeitsbereich verlässt.

Wenn die Variable erstmals deklariert wird und ihr Wert weder null noch undefined ist, wird eine Freigabefunktion aus dem Objekt abgerufen. Zuerst wird die Eigenschaft [Symbol.asyncDispose] geprüft; ist sie undefined, wird auf [Symbol.dispose] zurückgegriffen. Enthält keine der beiden Eigenschaften eine Funktion, wird ein TypeError ausgelöst. Die [Symbol.dispose]()-Methode wird dabei in eine Funktion eingehüllt, die etwa wie async () => { object[Symbol.dispose](); } aussieht. Gibt die Methode ein Promise zurück, wird daher nicht auf dieses Promise gewartet. Die Freigabefunktion wird für den Gültigkeitsbereich gespeichert.

Wenn die Variable ihren Gültigkeitsbereich verlässt, wird die Freigabefunktion aufgerufen und auf ihr Ergebnis gewartet. Enthält der Gültigkeitsbereich mehrere using- oder await using-Deklarationen, werden alle Freigabefunktionen unabhängig von der Art der Deklaration nacheinander in umgekehrter Deklarationsreihenfolge ausgeführt. Die Ausführung aller Freigabefunktionen ist garantiert (ähnlich wie beim finally-Block in try...catch...finally). Werden mehrere Fehler ausgelöst, einschließlich des ursprünglichen Fehlers, der gegebenenfalls zum Verlassen des Gültigkeitsbereichs geführt hat, werden sie in einem SuppressedError zusammengefasst. Dabei wird jeweils die frühere Ausnahme als suppressed-Eigenschaft und die spätere als error-Eigenschaft gespeichert. Der SuppressedError wird ausgelöst, nachdem die Freigabe abgeschlossen ist.

Die Variable darf den Wert null oder undefined haben, sodass die Ressource optional vorhanden sein kann. Sobald in einem Gültigkeitsbereich mindestens eine await using-Variable deklariert ist, findet beim Verlassen des Gültigkeitsbereichs garantiert mindestens eine await-Operation statt, selbst wenn die Variable tatsächlich den Wert null oder undefined hat. Dadurch wird verhindert, dass die Freigabe synchron erfolgt und Probleme mit der zeitlichen Abfolge entstehen (siehe Auswirkungen von await auf den Kontrollfluss).

await using bindet die Ressourcenverwaltung an lexikalische Gültigkeitsbereiche. Das ist praktisch, kann aber mitunter verwirrend sein. Weiter unten finden Sie Beispiele, in denen sich die Deklaration möglicherweise anders verhält als erwartet. Wenn Sie die Freigabe von Ressourcen selbst steuern möchten, ohne auf dieselben Garantien bei der Fehlerbehandlung zu verzichten, können Sie stattdessen AsyncDisposableStack verwenden.

Beispiele

Weitere Beispiele finden Sie unter using, insbesondere zu allgemeinen Fallstricken bei der Verwaltung von Ressourcen anhand von Gültigkeitsbereichen.

Grundlegende Verwendung

Üblicherweise verwenden Sie await using für eine von einer Bibliothek bereitgestellte Ressource, die das Protokoll für asynchrone Freigabe bereits implementiert. Beispielsweise kann das Node.js-FileHandle asynchron freigegeben werden:

js
import fs from "node:fs/promises";

async function example() {
  await using file = await fs.open("example.txt", "r");
  console.log(await file.read());
  // Before `file` goes out of scope, it is disposed by calling `file[Symbol.asyncDispose]()` and awaited.
}

Beachten Sie, dass die Deklaration von file zwei await-Operationen enthält. Sie erfüllen unterschiedliche Aufgaben und sind beide erforderlich. await fs.open() bewirkt eine Warteoperation beim Erwerben der Ressource: Es wird gewartet, bis die Datei geöffnet ist, und das zurückgegebene Promise wird in ein FileHandle-Objekt aufgelöst. await using file bewirkt eine Warteoperation bei der Freigabe: file wird asynchron freigegeben, wenn die Variable ihren Gültigkeitsbereich verlässt.

await using mit for await...of

Die folgenden drei Syntaxformen lassen sich leicht verwechseln:

  • for await (using x of y) { ... }
  • for (await using x of y) { ... }
  • for (using x of await y) { ... }

Noch verwirrender ist möglicherweise, dass sie miteinander kombiniert werden können.

js
for await (await using x of await y) {
  // ...
}

Zunächst bewirkt await y das Erwartete: Mit await warten wir auf das Promise y, das zu einem Objekt aufgelöst werden soll, über das wir iterieren. Diese Variante lassen wir im Folgenden außer Acht.

Die for await...of-Schleife setzt voraus, dass das Objekt y asynchron iterierbar ist. Ein asynchron iterierbares Objekt besitzt eine [Symbol.asyncIterator]-Methode, die einen asynchronen Iterator zurückgibt. Dessen next()-Methode gibt ein Promise zurück, das das Ergebnis repräsentiert. Dies ist für Fälle gedacht, in denen das iterierbare Objekt erst nach Abschluss einer asynchronen Operation weiß, was der nächste Wert ist oder ob die Iteration bereits beendet ist. (Ist [Symbol.asyncIterator] null oder undefined, verwendet die Schleife stattdessen [Symbol.iterator] und verpackt jedes Ergebnis in ein Promise.)

Die Syntax await using x setzt hingegen voraus, dass das vom iterierbaren Objekt gelieferte Objekt x asynchron freigegeben werden kann. Ein solches Objekt besitzt eine [Symbol.asyncDispose]-Methode, die ein Promise zurückgibt, das die Freigabeoperation repräsentiert. (Ist [Symbol.asyncDispose] null oder undefined, wird stattdessen [Symbol.dispose] verwendet.) Dies ist unabhängig von der Iteration selbst; die Freigabefunktion wird erst aufgerufen, wenn die Variable x ihren Gültigkeitsbereich verlässt.

Mit anderen Worten: Alle folgenden vier Kombinationen sind gültig und bewirken Unterschiedliches:

  • for (using x of y): Über y wird synchron iteriert. Dabei wird jeweils ein Ergebnis geliefert, das synchron freigegeben werden kann.
  • for await (using x of y): Über y wird asynchron iteriert. Nach jeder Warteoperation wird ein Ergebnis geliefert, dessen Wert jedoch synchron freigegeben werden kann.
  • for (await using x of y): Über y wird synchron iteriert. Dabei wird jeweils ein Ergebnis geliefert, auf dessen Freigabe gewartet wird.
  • for await (await using x of y): Über y wird asynchron iteriert. Nach jeder Warteoperation wird ein Ergebnis geliefert, auf dessen Freigabe ebenfalls gewartet wird.

Im Folgenden erstellen wir einige fiktive Werte für y, um die jeweiligen Anwendungsfälle zu veranschaulichen. Für asynchrone APIs stützen wir unseren Code auf das Node.js-Modul fs/promises.

js
const syncIterableOfSyncDisposables = [
  stream1.getReader(),
  stream2.getReader(),
];
for (using reader of syncIterableOfSyncDisposables) {
  console.log(reader.read());
}
js
async function* requestMany(urls) {
  for (const url of urls) {
    const res = await fetch(url);
    yield res.body.getReader();
  }
}
const asyncIterableOfSyncDisposables = requestMany([
  "https://example.com",
  "https://example.org",
]);
for await (using reader of asyncIterableOfSyncDisposables) {
  console.log(reader.read());
}
js
import fs from "node:fs";
import fsPromises from "node:fs/promises";

const syncIterableOfAsyncDisposables = await Promise.all(
  fs.globSync("*.txt").map((path) => fsPromises.open(path, "r")),
);
for (await using file of syncIterableOfAsyncDisposables) {
  console.log(await file.readFile("utf8"));
}
js
import fs from "node:fs/promises";

async function* globHandles(pattern) {
  for await (const path of fs.glob(pattern)) {
    yield await fs.open(path, "r");
  }
}
const asyncIterableOfAsyncDisposables = globHandles("*.txt");
for await (await using file of asyncIterableOfAsyncDisposables) {
  console.log(await file.readFile("utf8"));
}

Implizites await beim Verlassen des Gültigkeitsbereichs

Sobald in einem Gültigkeitsbereich eine await using-Variable deklariert wird, findet beim Verlassen des Gültigkeitsbereichs immer eine await-Operation statt, auch wenn die Variable null oder undefined ist. Dies gewährleistet eine stabile Ausführungsreihenfolge und Fehlerbehandlung. Die Beispiele zu den Auswirkungen von await auf den Kontrollfluss erläutern dies genauer.

Im folgenden Beispiel wird der Aufruf example() erst einen Tick später abgeschlossen, da bei der Rückkehr aus der Funktion ein implizites await stattfindet.

js
async function example() {
  await using nothing = null;
  console.log("Example call");
}

example().then(() => console.log("Example done"));
Promise.resolve().then(() => console.log("Microtask done"));
// Output:
// Example call
// Microtask done
// Example done

Betrachten Sie denselben Code, aber mit einem synchronen using. Diesmal wird der Aufruf example() sofort abgeschlossen, sodass die beiden then()-Handler im selben Tick aufgerufen werden.

js
async function example() {
  using nothing = null;
  console.log("Example call");
}

example().then(() => console.log("Example done"));
Promise.resolve().then(() => console.log("Microtask done"));
// Output:
// Example call
// Example done
// Microtask done

Ein praxisnäheres Beispiel sind zwei gleichzeitig stattfindende Aufrufe einer Funktion:

js
class Resource {
  #name;
  constructor(name) {
    this.#name = name;
  }
  async [Symbol.asyncDispose]() {
    console.log(`Disposing resource ${this.#name}`);
  }
}

async function example(id, createOptionalResource) {
  await using required = new Resource(`required ${id}`);
  await using optional = createOptionalResource
    ? new Resource("optional")
    : null;
  await using another = new Resource(`another ${id}`);
}

example(1, true);
example(2, false);
// Output:
// Disposing resource another 1
// Disposing resource another 2
// Disposing resource optional
// Disposing resource required 1
// Disposing resource required 2

Wie Sie sehen, wird die Ressource required 2 im selben Tick wie required 1 freigegeben. Würde die Ressource optional keine zusätzliche await-Operation bewirken, würde required 2 früher freigegeben, nämlich gleichzeitig mit optional.

Spezifikationen

Spezifikation
ECMAScript Async Explicit Resource Management
# sec-let-and-const-declarations

Browser-Kompatibilität

Siehe auch