Dieser Inhalt wurde automatisch aus dem Englischen übersetzt, und kann Fehler enthalten.Erfahre mehr über dieses Experiment.
JavaScript-Ressourcenverwaltung
Dieser Leitfaden behandelt, wie man in JavaScriptRessourcenverwaltung durchführt. Ressourcenverwaltung ist nicht genau dasselbe wieSpeicherverwaltung, welches ein fortgeschritteneres Thema ist und normalerweise automatisch von JavaScript gehandhabt wird. Ressourcenverwaltung dreht sich um die Verwaltung von Ressourcen, dienicht automatisch von JavaScript aufgeräumt werden. Manchmal ist es in Ordnung, einige ungenutzte Objekte im Speicher zu haben, da sie nicht in die Anwendungslogik eingreifen, aber Ressourcenlecks führen oft dazu, dass Dinge nicht funktionieren oder der Speicherverbrauch stark zunimmt. Daher ist dies keine optionale Optimierungsfunktion, sondern eine grundlegende Funktion zum Schreiben korrekter Programme!
Hinweis:Auch wenn Speicherverwaltung und Ressourcenverwaltung zwei getrennte Themen sind, können Sie manchmal als letzte Möglichkeit in das Speichermanagementsystem eingreifen, um Ressourcenverwaltung durchzuführen. Wenn Sie beispielsweise über ein JavaScript-Objekt verfügen, das einen Handle einer externen Ressource darstellt, können Sie einFinalizationRegistry erstellen, um die Ressource zu bereinigen, wenn der Handle vom Garbage Collector entfernt wird, da es definitiv keine Möglichkeit gibt, auf die Ressource danach zuzugreifen. Es gibt jedoch keine Garantie, dass der Finalizer ausgeführt wird, daher ist es keine gute Idee, sich für kritische Ressourcen darauf zu verlassen.
In diesem Artikel
Problem
Betrachten wir zunächst einige Beispiele von Ressourcen, die verwaltet werden müssen:
Datei-Handles: Ein Datei-Handle wird verwendet, um Bytes in einer Datei zu lesen und zu schreiben. Wenn Sie damit fertig sind, müssen Sie
fileHandle.close()aufrufen, andernfalls bleibt die Datei offen, selbst wenn das JS-Objekt nicht mehr zugänglich ist. Wie in der verlinkten Node.js-Dokumentation erwähnt:Wenn ein
<FileHandle>nicht mittels derfileHandle.close()-Methode geschlossen wird, versucht es, den Dateideskriptor automatisch zu schließen und eine Prozesswarnung auszugeben, um Speicherlecks zu verhindern. Bitte verlassen Sie sich nicht auf dieses Verhalten, da es unzuverlässig sein kann und die Datei möglicherweise nicht geschlossen wird. Schließen Sie<FileHandle>s stattdessen immer explizit. Node.js kann dieses Verhalten in Zukunft ändern.Netzwerkverbindungen: Einige Verbindungen, wie
WebSocketundRTCPeerConnection, müssen geschlossen werden, wenn keine Nachrichten übertragen werden. Andernfalls bleibt die Verbindung offen, und Verbindungspools sind oft sehr begrenzt in ihrer Größe.Stream-Leser: Wenn Sie
ReadableStreamDefaultReader.releaseLock()nicht aufrufen, wird der Stream gesperrt und erlaubt es einem anderen Leser nicht, ihn zu konsumieren.
Hier ist ein konkretes Beispiel, das einen lesbaren Stream verwendet:
const stream = new ReadableStream({ start(controller) { controller.enqueue("a"); controller.enqueue("b"); controller.enqueue("c"); controller.close(); },});async function readUntil(stream, text) { const reader = stream.getReader(); let chunk = await reader.read(); while (!chunk.done && chunk.value !== text) { console.log(chunk); chunk = await reader.read(); } // We forgot to release the lock here}readUntil(stream, "b").then(() => { const anotherReader = stream.getReader(); // TypeError: ReadableStreamDefaultReader constructor can only // accept readable streams that are not yet locked to a reader});Hier haben wir einen Stream, der drei Datenstücke ausgibt. Wir lesen vom Stream, bis wir den Buchstaben "b" finden. WennreadUntil zurückkehrt, ist der Stream nur teilweise verbraucht, so dass wir in der Lage sein sollten, weiterhin mit einem anderen Leser daraus zu lesen. Wir haben jedoch vergessen, die Sperre freizugeben, sodass der Stream noch gesperrt ist und wir keinen weiteren Leser erstellen können, obwohlreader nicht mehr verfügbar ist.
Die Lösung in diesem Fall ist einfach: Rufen Siereader.releaseLock() am Ende vonreadUntil auf. Aber einige Probleme bleiben bestehen:
Inkonsistenz: Verschiedene Ressourcen haben verschiedene Möglichkeiten, sie freizugeben. Zum Beispiel haben wir
close(),releaseLock(),disconnect()usw. Das Muster verallgemeinert sich nicht.Fehlerbehandlung: Was passiert, wenn der Aufruf von
reader.read()fehlschlägt? Dann würdereadUntilenden und nie zum Aufruf vonreader.releaseLock()gelangen. Wir können dies mittry...finallybeheben:jsasync function readUntil(stream, text) { const reader = stream.getReader(); try { let chunk = await reader.read(); while (!chunk.done && chunk.value !== text) { console.log(chunk); chunk = await reader.read(); } } finally { reader.releaseLock(); }}Aber Sie müssen daran denken, dies jedes Mal zu tun, wenn Sie einige wichtige Ressourcen freigeben müssen.
Scoping: Im obigen Beispiel ist
readerbereits geschlossen, wenn wir dietry...finally-Anweisung verlassen, aber es ist weiterhin in seinem Gültigkeitsbereich verfügbar. Das bedeutet, dass Sie es möglicherweise versehentlich verwenden, nachdem es geschlossen wurde.Mehrere Ressourcen: Wenn wir zwei Leser auf verschiedenen Streams haben, müssen wir daran denken, beide freizugeben. Dies ist ein respektabler Versuch, dies zu tun:
jsconst reader1 = stream1.getReader();const reader2 = stream2.getReader();try { // do something with reader1 and reader2} finally { reader1.releaseLock(); reader2.releaseLock();}Dies führt jedoch zu weiteren Fehlerbehandlungsproblemen. Wenn
stream2.getReader()eine Ausnahme auslöst, wirdreader1nicht freigegeben; wennreader1.releaseLock()einen Fehler auslöst, wirdreader2nicht freigegeben. Dies bedeutet, dass wir tatsächlich jedes Ressourcenakquisitions-Freigabe-Paar in seiner eigenentry...finally-Anweisung einwickeln müssen:jsconst reader1 = stream1.getReader();try { const reader2 = stream2.getReader(); try { // do something with reader1 and reader2 } finally { reader2.releaseLock(); }} finally { reader1.releaseLock();}
Sie sehen, wie eine scheinbar harmlose Aufgabe des Aufrufs vonreleaseLock schnell zu verschachteltem Boilerplate-Code führen kann. Aus diesem Grund bietet JavaScript integrierte Sprachunterstützung für die Ressourcenverwaltung.
Dieusing undawait using Deklarationen
Die Lösung, die wir haben, sind zwei spezielle Arten von Variablendeklarationen:using undawait using. Sie sindconst ähnlich, aber sie geben die Ressource automatisch frei, wenn die Variable außer Gültigkeitsbereich gerät, solange die Ressourcewegwerfbar ist. Anhand des gleichen Beispiels wie oben können wir es so umschreiben:
{ using reader1 = stream1.getReader(); using reader2 = stream2.getReader(); // do something with reader1 and reader2 // Before we exit the block, reader1 and reader2 are automatically released}Hinweis:Zum Zeitpunkt des Schreibens implementiertReadableStreamDefaultReader nicht das Wegwerfprotokoll. Dies ist ein hypothetisches Beispiel.
Beachten Sie zuerst die zusätzlichen geschweiften Klammern um den Code. Dies erstellt einen neuenBlock Gültigkeitsbereich für dieusing-Deklarationen. Ressourcen, die mitusing deklariert wurden, werden automatisch freigegeben, wenn sie außerhalb des Gültigkeitsbereichs vonusing gelangen, was in diesem Fall immer dann der Fall ist, wenn wir den Block verlassen, entweder weil alle Anweisungen ausgeführt wurden oder weil irgendwo ein Fehler oderreturn/break/continue vorliegt.
Das bedeutet,using kann nur in einem Gültigkeitsbereich verwendet werden, der eine klare Lebensdauer hat – nämlich kann es nicht auf der obersten Ebene eines Skripts verwendet werden, da Variablen auf der obersten Ebene eines Skripts im Gültigkeitsbereich für alle zukünftigen Skripte auf der Seite sind, was praktisch bedeutet, dass die Ressource nie freigegeben werden kann, wenn die Seite nie geladen wird. Sie können es jedoch auf der obersten Ebene einesModuls verwenden, da der Modulbereich endet, wenn das Modul die Ausführung beendet.
Jetzt wissen wir,wannusing aufräumt. Aberwie wird das gemacht?using erfordert, dass die Ressource daswegwerfbare Protokoll implementiert. Ein Objekt ist wegwerfbar, wenn es die Methode[Symbol.dispose]() besitzt. Diese Methode wird ohne Argumente aufgerufen, um eine Bereinigung durchzuführen. Im Falle des Lesers kann die[Symbol.dispose]-Eigenschaft ein einfacher Alias oder Wrapper vonreleaseLock sein:
// For demonstrationclass MyReader { // A wrapper [Symbol.dispose]() { this.releaseLock(); } releaseLock() { // Logic to release resources }}// OR, an aliasMyReader.prototype[Symbol.dispose] = MyReader.prototype.releaseLock;Durch das Wegwerfprotokoll kannusing alle Ressourcen auf konsistente Weise entsorgen, ohne zu verstehen, um welche Art von Ressource es sich handelt.
Jeder Gültigkeitsbereich hat eine Liste von Ressourcen, die mit ihm verbunden sind, in der Reihenfolge, in der sie deklariert wurden. Wenn der Bereich verlassen wird, werden die Ressourcen in umgekehrter Reihenfolge entsorgt, indem ihre Methode[Symbol.dispose]() aufgerufen wird. Zum Beispiel wird im obigen Beispielreader1 vorreader2 deklariert, also wirdreader2 zuerst, dannreader1 entsorgt. Fehler, die beim Versuch, eine Ressource zu entsorgen, auftreten, verhindern nicht die Entsorgung anderer Ressourcen. Dies ist konsistent mit demtry...finally-Muster und respektiert mögliche Abhängigkeiten zwischen den Ressourcen.
await using ist sehr ähnlich zuusing. Die Syntax sagt Ihnen, dass einawait irgendwo passiert – nicht wenn die Ressource deklariert wird, sondern tatsächlich, wenn sie entsorgt wird.await using erfordert, dass die Ressourceasynchron wegwerfbar ist, was bedeutet, dass sie eine Methode[Symbol.asyncDisposable]() hat. Diese Methode wird ohne Argumente aufgerufen und gibt ein Versprechen zurück, das aufgelöst wird, wenn die Bereinigung abgeschlossen ist. Dies ist nützlich, wenn die Bereinigung asynchron ist, wiefileHandle.close(), in welchem Fall das Ergebnis der Entsorgung nur asynchron bekannt sein kann.
{ await using fileHandle = open("file.txt", "w"); await fileHandle.write("Hello"); // fileHandle.close() is called and awaited}Daawait using erfordert, einawait auszuführen, ist es nur in Kontexten zulässig, in denenawait ist, was innerhalb vonasync-Funktionen und auf oberer Ebene vonawait in Modulen enthalten ist.
Ressourcen werden nacheinander und nicht gleichzeitig aufgeräumt: Der Rückgabewert der[Symbol.asyncDispose]()-Methode einer Ressource wirdawaitet, bevor die nächste Ressource mit ihrer[Symbol.asyncDispose]()-Methode aufgerufen wird.
Einige Dinge zu beachten:
usingundawait usingsindOpt-in. Wenn Sie Ihre Ressource mitlet,constodervardeklarieren, erfolgt keine automatische Entsorgung, genau wie bei anderen nicht wegwerfbaren Werten.usingundawait usingerfordern, dass die Ressource wegwerfbar (oder asynchron wegwerfbar) ist. Wenn die Ressource die Methode[Symbol.dispose]()oder[Symbol.asyncDispose]()nicht hat, erhalten Sie einenTypeErrorin der Deklarationszeile. Die Ressource kann jedochnulloderundefinedsein, sodass Sie Ressourcen bedingt erwerben können.- Wie
constkönnenusingundawait usingVariablen nicht neu zugewiesen werden, obwohl die Eigenschaften der Objekte, die sie enthalten, geändert werden können. Die Methode[Symbol.dispose]()/[Symbol.asyncDispose]()wird jedoch bereits zum Zeitpunkt der Deklaration gespeichert, sodass eine Änderung der Methode nach der Deklaration die Bereinigung nicht beeinflusst. - Es gibt einige Fallstricke, wenn man Gültigkeitsbereiche mit der Lebensdauer von Ressourcen vermischt. Siehe
usingfür einige Beispiele.
DieDisposableStack undAsyncDisposableStack Objekte
using undawait using sind spezielle Syntaxen. Syntaxen sind bequem und verbergen viel der Komplexität, aber manchmal müssen Sie Dinge manuell erledigen.
Ein häufiges Beispiel: Was, wenn Sie die Ressource nicht am Endedieses Gültigkeitsbereichs entsorgen möchten, sondern in einemspäteren Gültigkeitsbereich? Bedenken Sie dies:
let reader;if (someCondition) { reader = stream.getReader();} else { reader = stream.getReader({ mode: "byob" });}Wie gesagt,using ist wieconst: es muss initialisiert werden und kann nicht neu zugewiesen werden, also könnten Sie versuchen, dies zu tun:
if (someCondition) { using reader = stream.getReader();} else { using reader = stream.getReader({ mode: "byob" });}Dies bedeutet jedoch, dass alle Logik innerhalb desif oderelse geschrieben werden muss, was zu vielen Duplikaten führt. Was wir tun möchten, ist die Ressource in einem Gültigkeitsbereich zu erwerben und zu registrieren, aber sie in einem anderen zu entsorgen. Wir können einenDisposableStack für diesen Zweck verwenden, der ein Objekt ist, das eine Sammlung von wegwerfbaren Ressourcen enthält und selbst wegwerfbar ist:
{ using disposer = new DisposableStack(); let reader; if (someCondition) { reader = disposer.use(stream.getReader()); } else { reader = disposer.use(stream.getReader({ mode: "byob" })); } // Do something with reader // Before scope exit, disposer is disposed, which disposes reader}Vielleicht haben Sie eine Ressource, die noch nicht das wegwerfbare Protokoll implementiert, sie wird also vonusing abgelehnt. In diesem Fall können Sieadopt() verwenden.
{ using disposer = new DisposableStack(); // Suppose reader does not have the [Symbol.dispose]() method, // then it cannot be used with using. // However, we can manually pass a disposer function to disposer.adopt const reader = disposer.adopt(stream.getReader(), (reader) => reader.releaseLock(), ); // Do something with reader // Before scope exit, disposer is disposed, which disposes reader}Vielleicht haben Sie eine Entsorgungsaktion auszuführen, aber sie ist nicht an eine bestimmte Ressource "gebunden". Vielleicht möchten Sie nur eine Nachricht protokollieren, die sagt "Alle Datenbankverbindungen geschlossen", wenn mehrere Verbindungen gleichzeitig geöffnet sind. In diesem Fall können Siedefer() verwenden.
{ using disposer = new DisposableStack(); disposer.defer(() => console.log("All database connections closed")); const connection1 = disposer.use(openConnection()); const connection2 = disposer.use(openConnection()); // Do something with connection1 and connection2 // Before scope exit, disposer is disposed, which first disposes connection1 // and connection2 and then logs the message}Vielleicht möchten Sie einebedingte Entsorgung durchführen – zum Beispiel nur beanspruchte Ressourcen entsorgen, wenn ein Fehler aufgetreten ist. In diesem Fall können Siemove() verwenden, um die Ressourcen zu erhalten, die ansonsten entsorgt würden.
class MyResource { #resource1; #resource2; #disposables; constructor() { using disposer = new DisposableStack(); this.#resource1 = disposer.use(getResource1()); this.#resource2 = disposer.use(getResource2()); // If we made it here, then there were no errors during construction and // we can safely move the disposables out of `disposer` and into `#disposables`. this.#disposables = disposer.move(); // If construction failed, then `disposer` would be disposed before reaching // the line above, disposing `#resource1` and `#resource2`. } [Symbol.dispose]() { this.#disposables.dispose(); // Dispose `#resource2` and `#resource1`. }}AsyncDisposableStack ist wieDisposableStack, aber zur Verwendung mit asynchron wegwerfbaren Ressourcen. Seineuse()-Methode erwartet einen asynchronen Wegwerfer, seineadopt()-Methode erwartet eine asynchrone Bereinigungsfunktion und seinedispose()-Methode erwartet einen asynchronen Rückruf. Es stellt eine[Symbol.asyncDispose]()-Methode bereit. Sie können ihm weiterhin synchrone Ressourcen übergeben, wenn Sie eine Mischung aus synchronen und asynchronen haben.
Die Referenz fürDisposableStack enthält weitere Beispiele und Details.
Fehlerbehandlung
Ein Hauptanwendungsfall der Ressourcenverwaltungsfunktion besteht darin, sicherzustellen, dass Ressourcen immer freigegeben werden, selbst wenn ein Fehler auftritt. Lassen Sie uns einige komplexe Fehlermanagementszenarien untersuchen.
Wir beginnen mit dem folgenden Code, der durch die Verwendung vonusing robust gegenüber Fehlern ist:
async function readUntil(stream, text) { // Use `using` instead of `await using` because `releaseLock` is synchronous using reader = stream.getReader(); let chunk = await reader.read(); while (!chunk.done && chunk.value !== text) { console.log(chunk.toUpperCase()); chunk = await reader.read(); }}Angenommen,chunk stellt sich alsnull heraus. Dann wird!chunk.done einenTypeError auslösen, der dazu führt, dass die Funktion beendet wird. Bevor die Funktion beendet wird, wirdstream[Symbol.dispose]() aufgerufen, was die Sperre auf den Stream freigibt.
const stream = new ReadableStream({ start(controller) { controller.enqueue("a"); controller.enqueue(null); controller.enqueue("b"); controller.enqueue("c"); controller.close(); },});readUntil(stream, "b") .catch((e) => console.error(e)) // TypeError: chunk.toUpperCase is not a function .then(() => { const anotherReader = stream.getReader(); // Successfully creates another reader });Somit verschlucktusing keine Fehler: Alle auftretenden Fehler werden weiterhin geworfen, aber die Ressourcen werden direkt vorher geschlossen. Was passiert nun, wenn die Ressourcenbereinigung selbst auch einen Fehler auslöst? Lassen Sie uns ein konstruiertes Beispiel verwenden:
class MyReader { [Symbol.dispose]() { throw new Error("Failed to release lock"); }}function doSomething() { using reader = new MyReader(); throw new Error("Failed to read");}try { doSomething();} catch (e) { console.error(e); // SuppressedError: An error was suppressed during disposal}ImdoSomething()-Aufruf werden zwei Fehler generiert: ein Fehler, der währenddoSomething ausgelöst wird, und ein Fehler, der während der Entsorgung vonreader aufgrund des ersten Fehlers ausgelöst wird. Beide Fehler werden gemeinsam geworfen, so dass das, was Sie abgefangen haben, einSuppressedError ist. Dies ist ein spezieller Fehler, der zwei Fehler umhüllt: Die Eigenschafterror enthält den späteren Fehler, und die Eigenschaftsuppressed enthält den früheren Fehler, der durch den späteren Fehler "unterdrückt" wird.
Wenn wir mehr als eine Ressource haben undbeide von ihnen während der Entsorgung einen Fehler werfen (was äußerst selten sein sollte – es ist bereits selten, dass die Entsorgung fehlschlägt!), dann wird jeder frühere Fehler durch den späteren Fehler unterdrückt, wodurch eine Kette von unterdrückten Fehlern entsteht.
class MyReader { [Symbol.dispose]() { throw new Error("Failed to release lock on reader"); }}class MyWriter { [Symbol.dispose]() { throw new Error("Failed to release lock on writer"); }}function doSomething() { using reader = new MyReader(); using writer = new MyWriter(); throw new Error("Failed to read");}try { doSomething();} catch (e) { console.error(e); // SuppressedError: An error was suppressed during disposal console.error(e.suppressed); // SuppressedError: An error was suppressed during disposal console.error(e.error); // Error: Failed to release lock on reader console.error(e.suppressed.suppressed); // Error: Failed to read console.error(e.suppressed.error); // Error: Failed to release lock on writer}- Der
readerwird zuletzt freigegeben, sodass sein Fehler der neueste ist und alles andere unterdrückt: Er wird alse.errorangezeigt. - Der
writerwird zuerst freigegeben, sodass sein Fehler später als der ursprüngliche austretende Fehler ist, aber früher als derreader-Fehler: Er wird alse.suppressed.errorangezeigt. - Der ursprüngliche Fehler über "Failed to read" ist der früheste Fehler, sodass er als
e.suppressed.suppressedangezeigt wird.
Beispiele
>Automatisches Freigeben von Objekt-URLs
Im folgenden Beispiel erstellen wir eineObjekt-URL zu einem Blob (in einer realen Anwendung würde dieser Blob von irgendwoher abgerufen werden, z. B. eine Datei oder eine Fetch-Antwort), so dass wir den Blob als Datei herunterladen können. Um ein Ressourcenleck zu verhindern, müssen wir die Objekt-URL mitURL.revokeObjectURL() freigeben, wenn sie nicht mehr benötigt wird (das heißt, wenn der Download erfolgreich gestartet wurde). Da die URL selbst nur eine Zeichenkette ist und daher nicht das wegwerfbare Protokoll implementiert, können wirurl nicht direkt mitusing deklarieren; daher erstellen wir einenDisposableStack, um als Entsorger fürurl zu dienen. Die Objekt-URL wird freigegeben, sobalddisposer außer Gültigkeitsbereich gerät, was entweder der Fall ist, wennlink.click() beendet ist oder irgendwo ein Fehler auftritt.
const downloadButton = document.getElementById("download-button");const exampleBlob = new Blob(["example data"]);downloadButton.addEventListener("click", () => { using disposer = new DisposableStack(); const link = document.createElement("a"); const url = disposer.adopt( URL.createObjectURL(exampleBlob), URL.revokeObjectURL, ); link.href = url; link.download = "example.txt"; link.click();});Automatisches Abbrechen laufender Anfragen
Im folgenden Beispiel verwenden wirfetch, um eine Liste von Ressourcen mit Hilfe vonPromise.all() gleichzeitig abzurufen.Promise.all() schlägt fehl und lehnt das resultierende Versprechen ab, sobald eine Anfrage fehlgeschlagen ist; jedoch laufen die anderen ausstehenden Anfragen weiter, obwohl ihre Ergebnisse für das Programm nicht zugänglich sind. Um zu verhindern, dass diese verbleibenden Anfragen unnötig Ressourcen verbrauchen, müssen wir automatisch laufende Anfragen abbrechen, wann immerPromise.all() abgeschlossen wird. Wir implementieren die Stornierung mit einemAbortController und übergeben dessensignal jedemfetch()-Aufruf. WennPromise.all() erfüllt wird, gibt die Funktion normal zurück und der Controller bricht ab, was harmlos ist, da es keine ausstehende Anfrage zu stornieren gibt; wennPromise.all() abgelehnt und die Funktion geworfen wird, bricht der Controller ab und storniert alle ausstehenden Anfragen.
async function getAllData(urls) { using disposer = new DisposableStack(); const { signal } = disposer.adopt(new AbortController(), (controller) => controller.abort(), ); // Fetch all URLs in parallel // Automatically cancel any incomplete requests if any request fails const pages = await Promise.all( urls.map((url) => fetch(url, { signal }).then((response) => { if (!response.ok) throw new Error( `Response error: ${response.status} - ${response.statusText}`, ); return response.text(); }), ), ); return pages;}Fallgruben
Die Ressourcenerstellungssyntax bietet viele starke Fehlerbehandlungsgarantien, die sicherstellen, dass die Ressourcen immer aufgeräumt werden, egal was passiert, aber es gibt einige Fallstricke, auf die Sie dennoch stoßen könnten:
- Vergessen,
usingoderawait usingzu verwenden. Die Ressourcenverwaltungssyntax ist nur dazu da, Ihnen zu helfen, wenn Sie wissen, dass Sie sie benötigen, aber es gibt nichts, das Sie daran erinnert, wenn Sie vergessen, sie zu verwenden! Leider gibt es keinen guten Weg, um dies vorab zu verhindern, da es keine syntaktischen Hinweise darauf gibt, dass etwas eine wegwerfbare Ressource ist, und selbst bei wegwerfbaren Ressourcen möchten Sie sie möglicherweise ohne automatische Entsorgung deklarieren. Wahrscheinlich benötigen Sie einen Typrüfgerät in Kombination mit einem Linter, um diese Probleme zu erkennen, wietypescript-eslint (welches noch plant, an dieser Funktion zu arbeiten). - Verwendung nach Freigabe. Im Allgemeinen stellt die
using-Syntax sicher, dass eine Ressource freigegeben wird, wenn sie außer Gültigkeitsbereich gerät, aber es gibt viele Möglichkeiten, einen Wert über seine Bindungsvariable hinaus zu erhalten. JavaScript verfügt nicht über einen Besitzmechanismus wie Rust, sodass Sie ein Alias deklarieren können, derusingnicht verwendet, oder die Ressource in einerSchließung speichern usw. Derusing-Referenz enthält viele Beispiele für solche Fallstricke. Auch hier gibt es keinen guten Weg, um dies in einem komplizierten Kontrollfluss richtig zu erkennen, daher müssen Sie vorsichtig sein.
Die Ressourcenverwaltungsfunktion ist kein Allheilmittel. Sie ist definitiv eine Verbesserung gegenüber dem manuellen Aufruf der Entsorgungsmethoden, aber sie ist nicht intelligent genug, um alle Ressourcenverwaltungsfehler zu verhindern. Sie müssen dennoch vorsichtig sein und die Semantik der von Ihnen verwendeten Ressourcen verstehen.
Fazit
Hier sind die Schlüsselelemente des Ressourcenverwaltungssystems:
usingundawait usingDeklarationen zur automatischen Ressourcenentsorgung.- Diewegwerfbare undasynchron wegwerfbare Protokolle, die jeweils das
Symbol.disposeundSymbol.asyncDisposeverwenden, um von Ressourcen implementiert zu werden. - Die
DisposableStackundAsyncDisposableStackObjekte für Fälle, in denenusingundawait usingnicht geeignet sind.
Mit der richtigen Nutzung dieser APIs können Sie Systeme erstellen, die mit externen Ressourcen interagieren und unter allen Fehlerbedingungen robust bleiben, ohne dass viel Boilerplate-Code erforderlich ist.