Signatur
Beschreibung
DisposableStack ist Teil des TC39-Proposals Explicit Resource Management und ermöglicht strukturiertes, vorhersehbares Freigeben von Ressourcen nach dem LIFO-Prinzip (Last In, First Out). Ressourcen werden dem Stapel hinzugefügt und beim Aufruf von dispose() – oder automatisch beim Verlassen eines using-Blocks – in umgekehrter Reihenfolge bereinigt.
Typische Anwendungsfälle sind das Schließen von Datenbankverbindungen, Entfernen von Event-Listenern, Freigeben nativer Handles oder das Zusammenfassen mehrerer Cleanup-Schritte in einem einzigen verwaltbaren Objekt. Durch die Kombination mit der using-Deklaration (ebenfalls Teil desselben Proposals) wird die Ressourcenverwaltung erheblich vereinfacht und Fehler durch vergessene Cleanups werden vermieden.
DisposableStack implementiert das Symbol.dispose-Protokoll und kann daher selbst als Ressource in einem using-Block verwendet werden. Intern werden alle registrierten Einträge in einem Stapel gehalten; beim Entsorgen werden sie der Reihe nach von oben nach unten (also in umgekehrter Registrierungsreihenfolge) aufgerufen.
Für asynchrone Cleanup-Operationen steht das Gegenstück AsyncDisposableStack zur Verfügung, das Symbol.asyncDispose implementiert und mit await using verwendet werden kann.
Rückgabewert
DisposableStack-Instanz mit einem leeren Stapel.Beispiele
Ressourcen mit use() registrieren und automatisch bereinigen
// Simulierte Ressource mit [Symbol.dispose]
class DatabaseConnection {
constructor(name) {
this.name = name;
console.log(`Verbindung geöffnet: ${this.name}`);
}
[Symbol.dispose]() {
console.log(`Verbindung geschlossen: ${this.name}`);
}
}
// using-Block: DisposableStack wird am Ende automatisch disposed
{
using stack = new DisposableStack();
const conn1 = stack.use(new DatabaseConnection('primary'));
const conn2 = stack.use(new DatabaseConnection('replica'));
console.log('Arbeite mit Verbindungen...');
// Am Ende des Blocks: conn2 wird zuerst geschlossen, dann conn1
}
Kombination von use(), adopt() und defer()
const cleanupLog = [];
// Ressource mit eigenem [Symbol.dispose]
const managedRes = {
[Symbol.dispose]() { cleanupLog.push('managedRes disposed'); }
};
// Rohe Ressource ohne Dispose-Protokoll
const rawHandle = { id: 42 };
const stack = new DisposableStack();
// use(): Ressource mit [Symbol.dispose] registrieren
stack.use(managedRes);
// adopt(): Rohe Ressource + separater Cleanup-Callback
stack.adopt(rawHandle, (handle) => {
cleanupLog.push(`rawHandle ${handle.id} freigegeben`);
});
// defer(): Reiner Callback ohne Ressource
stack.defer(() => {
cleanupLog.push('defer-Callback ausgeführt');
});
console.log('Vor dispose:', stack.disposed); // false
stack.dispose();
console.log('Nach dispose:', stack.disposed); // true
console.log(cleanupLog);
// Zweiter Aufruf ist eine No-Op
stack.dispose();
console.log('Kein Fehler beim zweiten dispose-Aufruf');
Stapel verschieben mit move()
function createResources() {
// Erstellt einen Stack und überträgt Verantwortung an den Aufrufer
const stack = new DisposableStack();
stack.defer(() => console.log('Ressource A bereinigt'));
stack.defer(() => console.log('Ressource B bereinigt'));
// move() überträgt alle Einträge in einen neuen Stack
// und markiert den ursprünglichen als disposed
return stack.move();
}
{
using resources = createResources();
console.log('Ressourcen aktiv...');
// Beim Verlassen des Blocks: automatisches dispose()
}
// Wichtig · Fallstricke
Experimenteller Status: DisposableStack befindet sich im TC39-Prozess auf Stage 3 (Stand 2024) und ist in V8 ab Version 12.x sowie Node.js 22+ hinter dem Flag --harmony-explicit-resource-management oder nativ ab Node.js 22.4+ verfügbar. TypeScript unterstützt das Feature ab Version 5.2. Produktionseinsatz sollte die jeweiligen Zielumgebungen prüfen.
Fehlerverhalten: Wenn ein Cleanup-Callback eine Exception wirft, werden die restlichen Callbacks trotzdem ausgeführt. Mehrere Exceptions werden zu einem SuppressedError zusammengefasst, das sowohl den ursprünglichen Fehler als auch den unterdrückten Fehler enthält.
Idempotenz: Ein bereits entsorgter Stack ignoriert weitere dispose()-Aufrufe stillschweigend. Der Zustand lässt sich über die disposed-Eigenschaft abfragen.
Asynchrone Operationen: Für Cleanups, die await benötigen, muss AsyncDisposableStack mit await using verwendet werden. Das synchrone DisposableStack unterstützt keine asynchronen Callbacks.