JavaScript-Ausführungsmodell
Diese Seite führt in die grundlegende Infrastruktur der JavaScript-Laufzeitumgebung ein. Das Modell ist weitgehend theoretisch und abstrakt, ohne plattformspezifische oder implementierungsspezifische Details. Moderne JavaScript-Engines optimieren die beschriebenen Semantiken stark.
Diese Seite dient als Referenz. Es wird davon ausgegangen, dass Sie bereits mit dem Ausführungsmodell anderer Programmiersprachen wie C und Java vertraut sind. Es wird stark auf bestehende Konzepte in Betriebssystemen und Programmiersprachen verwiesen.
Die Engine und der Host
Die Ausführung von JavaScript erfordert die Zusammenarbeit von zwei Softwareteilen: der JavaScript-Engine und der Host-Umgebung.
Die JavaScript-Engine implementiert die ECMAScript (JavaScript) Sprache und stellt die grundlegende Funktionalität bereit. Sie nimmt Quellcode, analysiert ihn und führt ihn aus. Um jedoch mit der Außenwelt zu interagieren, z. B. um sinnvolle Ausgaben zu erzeugen, Schnittstellen zu externen Ressourcen zu schaffen oder Sicherheits- oder Leistungsmechanismen zu implementieren, benötigen wir zusätzliche, umgebungsspezifische Mechanismen, die von der Host-Umgebung bereitgestellt werden. Zum Beispiel ist das HTML-DOM die Host-Umgebung, wenn JavaScript in einem Webbrowser ausgeführt wird. Node.js ist eine weitere Host-Umgebung, die es erlaubt, JavaScript auf der Serverseite auszuführen.
Während wir uns in dieser Referenz hauptsächlich auf die in ECMAScript definierten Mechanismen konzentrieren, werden wir gelegentlich über Mechanismen sprechen, die in der HTML-Spezifikation definiert sind und oft von anderen Host-Umgebungen wie Node.js oder Deno nachgeahmt werden. Auf diese Weise können wir ein kohärentes Bild des JavaScript-Ausführungsmodells sowohl im Web als auch darüber hinaus vermitteln.
Agentenausführungsmodell
In der JavaScript-Spezifikation wird jeder autonome Ausführer von JavaScript als Agent bezeichnet, der seine Einrichtungen zur Code-Ausführung verwaltet:
- Heap (von Objekten): Dies ist einfach ein Name, um einen großen (meist unstrukturierten) Speicherbereich zu bezeichnen. Dieser wird gefüllt, wenn Objekte im Programm erstellt werden. Beachten Sie, dass im Fall von gemeinsam genutztem Speicher jeder Agent seinen eigenen Heap mit seiner eigenen Version eines
SharedArrayBuffer-Objekts hat, aber der zugrunde liegende Speicher, den der Puffer repräsentiert, geteilt wird. - Queue (von Jobs): Dies wird in HTML (und auch allgemein) als Ereignisschleife bezeichnet, die asynchrones Programmieren in JavaScript ermöglicht, während es einthreading bleibt. Sie wird als Warteschlange bezeichnet, weil sie im Allgemeinen first-in-first-out ist: frühere Jobs werden vor späteren ausgeführt.
- Stack (von Ausführungskontexten): Dies ist das, was als Aufrufstapel bekannt ist und die Übertragung des Kontrollflusses durch Ein- und Ausstieg aus Ausführungskontexten wie Funktionen ermöglicht. Es wird als Stack bezeichnet, da es last-in-first-out ist. Jeder Job beginnt, indem er einen neuen Frame auf den (leeren) Stack schiebt, und endet, indem der Stack geleert wird.
Dies sind drei unterschiedliche Datenstrukturen, die unterschiedliche Daten nachverfolgen. Wir werden die Queue und den Stack in den folgenden Abschnitten genauer vorstellen. Um mehr darüber zu erfahren, wie Speicherheap zugewiesen und freigegeben wird, sehen Sie sich die Speicherverwaltung an.
Jeder Agent ist analog zu einem Thread (beachten Sie, dass die zugrunde liegende Implementierung möglicherweise kein tatsächlicher Betriebssystem-Thread ist oder nicht). Jeder Agent kann mehrere Realms besitzen (die 1-zu-1 mit globalen Objekten korrelieren), die synchron aufeinander zugreifen können und daher in einem einzigen Ausführungsthread laufen müssen. Ein Agent hat auch ein einzelnes Speichermodell, das angibt, ob es wenig-endian ist, ob es synchron blockiert werden kann, ob atomare Operationen sperrfrei sind usw.
Ein Agent im Web kann einer der folgenden sein:
- Ein Agent für ähnliche Herkunftsfenster, der verschiedene
Window-Objekte enthält, die potenziell aufeinander zugreifen können, entweder direkt oder durch Verwendung vondocument.domain. Wenn das Fenster ursprungsbasiert ist, können nur gleichursprüngliche Fenster aufeinander zugreifen. - Ein Dedizierter Worker-Agent mit einem einzigen
DedicatedWorkerGlobalScope. - Ein Gemeinsamer Worker-Agent mit einem einzigen
SharedWorkerGlobalScope. - Ein Service Worker-Agent mit einem einzigen
ServiceWorkerGlobalScope. - Ein Worklet-Agent mit einem einzigen
WorkletGlobalScope.
Mit anderen Worten, jeder Worker erstellt seinen eigenen Agenten, während ein oder mehrere Fenster innerhalb desselben Agenten sein können – in der Regel ein Hauptdokument und seine ähnliche Herkunfts-Iframes. In Node.js ist ein ähnliches Konzept namens Arbeitsthreads verfügbar.
Das folgende Diagramm illustriert das Ausführungsmodell von Agenten:
Realms
Jeder Agent besitzt ein oder mehrere Realms. Jeder JavaScript-Code ist mit einem Realm verbunden, wenn er geladen wird, was auch dann gleich bleibt, wenn er aus einem anderen Realm aufgerufen wird. Ein Realm besteht aus den folgenden Informationen:
- Eine Liste von intrinsischen Objekten wie
Array,Array.prototypeusw. - Global deklarierte Variablen, der Wert von
globalThisund das globale Objekt - Ein Cache von Template-String-Arrays, da die Auswertung desselben getaggten Template-String-Ausdrucks immer dazu führt, dass der Tag dasselbe Array-Objekt erhält
Im Web stehen das Realm und das globale Objekt in einer 1-zu-1-Korrespondenz. Das globale Objekt ist entweder ein Window, ein WorkerGlobalScope oder ein WorkletGlobalScope. Zum Beispiel führt jedes iframe in einem anderen Realm aus, obwohl es im selben Agenten wie das Elternfenster sein kann.
Realms werden häufig erwähnt, wenn es um die Identitäten globaler Objekte geht. Zum Beispiel benötigen wir Methoden wie Array.isArray() oder Error.isError(), weil ein in einem anderen Realm erstelltes Array ein anderes Prototypobjekt als das Array.prototype-Objekt im aktuellen Realm haben wird, sodass instanceof Array fälschlicherweise false zurückgibt.
Stack und Ausführungskontexte
Betrachten wir zunächst die synchrone Code-Ausführung. Jeder Job wird durch Aufrufen seines zugehörigen Rückrufs gestartet. Code innerhalb dieses Rückrufs kann Variablen erstellen, Funktionen aufrufen oder beendet werden. Jede Funktion muss ihre eigenen Variablenumgebungen und den Rücksprungpunkt speichern. Um damit umzugehen, benötigt der Agent einen Stack, um die Ausführungskontexte nachzuverfolgen. Ein Ausführungskontext, auch allgemein als Stack-Frame bekannt, ist die kleinste Ausführungseinheit. Es verfolgt die folgenden Informationen:
- Codeauswertungszustand
- Das Modul oder Skript, die Funktion (falls zutreffend) und der aktuell ausführende Generator, der diesen Code enthält
- Der aktuelle Realm
- Bindings, einschließlich:
- Mit
var,let,const,function,classusw. definierte Variablen - Private Bezeichner wie
#foo, die nur im aktuellen Kontext gültig sind this-Referenz
- Mit
Stellen Sie sich ein Programm vor, das aus einem einzigen Job besteht, der durch den folgenden Code definiert ist:
function foo(b) {
const a = 10;
return a + b + 11;
}
function bar(x) {
const y = 3;
return foo(x * y);
}
const baz = bar(7); // assigns 42 to baz
- Wenn der Job beginnt, wird der erste Frame erstellt, in dem die Variablen
foo,barundbazdefiniert sind. Er ruftbarmit dem Argument7auf. - Ein zweiter Frame wird für den
bar-Aufruf erstellt, der Bindungen für den Parameterxund die lokale Variableyenthält. Zuerst wird die Multiplikationx * ydurchgeführt und dannfoomit dem Ergebnis aufgerufen. - Ein dritter Frame wird für den
foo-Aufruf erstellt, der Bindungen für den Parameterbund die lokale Variableaenthält. Zuerst wird die Additiona + b + 11durchgeführt und dann das Ergebnis zurückgegeben. - Wenn
foozurückkehrt, wird das oberste Frame-Element aus dem Stack entfernt, und der Ausdruckfoo(x * y)löst sich in den Rückgabewert auf. Dann wird die Ausführung fortgesetzt, was nur bedeutet, dieses Ergebnis zurückzugeben. - Wenn
barzurückkehrt, wird das oberste Frame-Element aus dem Stack entfernt, und der Ausdruckbar(7)löst sich in den Rückgabewert auf. Dies initialisiertbazmit dem Rückgabewert. - Wir erreichen das Ende des Quellcodes des Jobs, sodass der Stack-Frame für den Einstiegspunkt aus dem Stack entfernt wird. Der Stack ist leer, also wird der Job als abgeschlossen betrachtet.
Generatoren und Wiedereintritt
Wenn ein Frame entfernt wird, ist es nicht unbedingt für immer weg, da wir manchmal zu ihm zurückkehren müssen. Betrachten Sie zum Beispiel eine Generatorfunktion:
function* gen() {
console.log(1);
yield;
console.log(2);
}
const g = gen();
g.next(); // logs 1
g.next(); // logs 2
In diesem Fall erstellt der Aufruf von gen() zuerst einen Ausführungskontext, der ausgesetzt wird – es wird kein Code innerhalb von gen ausgeführt. Der Generator g speichert diesen Ausführungskontext intern. Der aktuell laufende Ausführungskontext bleibt der Einstiegspunkt. Wenn g.next() aufgerufen wird, wird der Ausführungskontext für gen auf den Stack geschoben, und der Code innerhalb von gen wird bis zum yield-Ausdruck ausgeführt. Dann wird der Generator-Ausführungskontext ausgesetzt und aus dem Stack entfernt, wodurch die Kontrolle an den Einstiegspunkt zurückgegeben wird. Wenn g.next() erneut aufgerufen wird, wird der Generator-Ausführungskontext wieder auf den Stack geschoben, und der Code innerhalb von gen wird an der Stelle fortgesetzt, an der er unterbrochen wurde.
Tail-Calls
Ein im Standard definiertes Mechanismus ist der echte Tail-Call (PTC). Ein Funktionsaufruf ist ein Tail-Call, wenn der Aufrufer nach dem Aufruf nichts anderes tut, außer den Wert zurückzugeben:
function f() {
return g();
}
In diesem Fall ist der Aufruf von g ein Tail-Call. Wenn ein Funktionsaufruf in Tail-Position steht, muss die Engine den aktuellen Ausführungskontext verwerfen und ihn durch den Kontext des Tail-Calls ersetzen, anstelle eines neuen Frames für den g()-Aufruf zu pushen. Dies bedeutet, dass Tail-Rekursion nicht den Stapelgrößenbegrenzungen unterliegt:
function factorial(n, acc = 1) {
if (n <= 1) return acc;
return factorial(n - 1, n * acc);
}
In der Praxis verursacht das Verwerfen des aktuellen Frames Debugging-Probleme, da wenn g() einen Fehler auslöst, f nicht mehr im Stack ist und nicht im Stack-Trace erscheint. Derzeit implementiert nur Safari (JavaScriptCore) PTC, und sie haben einige spezifische Infrastruktur entwickelt, um das Debugging-Problem zu adressieren.
Closures
Ein weiteres interessantes Phänomen im Zusammenhang mit dem Variablescopen und Funktionsaufrufen sind Closures. Wann immer eine Funktion erstellt wird, speichert sie intern auch die Variablenbindungen des aktuell laufenden Ausführungskontexts. Diese Variablenbindungen können dann den Ausführungskontext überdauern.
let f;
{
let x = 10;
f = () => x;
}
console.log(f()); // logs 10
Job-Warteschlange und Ereignisschleife
Ein Agent ist ein Thread, was bedeutet, dass der Interpreter nur eine Anweisung gleichzeitig verarbeiten kann. Wenn der gesamte Code synchron ist, ist dies in Ordnung, da wir immer Fortschritte machen können. Aber wenn der Code eine asynchrone Aktion durchführen muss, können wir keinen Fortschritt machen, solange diese Aktion nicht abgeschlossen ist. Es wäre jedoch nachteilig für die Benutzererfahrung, wenn das das ganze Programm anhalten würde – die Natur von JavaScript als Web-Skriptsprache erfordert, dass es niemals blockiert. Daher wird der Code, der die Vollendung dieser asynchronen Aktion behandelt, als Rückruf definiert. Dieser Rückruf definiert einen Job, der in eine Job-Warteschlange – oder in HTML-Terminologie eine Ereignisschleife – aufgenommen wird, sobald die Aktion abgeschlossen ist.
Jedes Mal zieht der Agent einen Job aus der Warteschlange und führt ihn aus. Wenn der Job ausgeführt wird, kann er weitere Jobs erstellen, die am Ende der Warteschlange hinzugefügt werden. Jobs können auch durch den Abschluss von asynchronen Plattformmechanismen, wie Timern, I/O und Ereignissen, hinzugefügt werden. Ein Job gilt als abgeschlossen, wenn der Stack leer ist; dann wird der nächste Job aus der Warteschlange gezogen. Jobs können nicht mit gleichmäßiger Priorität gezogen werden – zum Beispiel teilen HTML-Ereignisschleifen Jobs in zwei Kategorien auf: Tasks und Microtasks. Microtasks haben höhere Priorität und die Microtask-Warteschlange wird zuerst geleert, bevor die Task-Warteschlange gezogen wird. Für weitere Informationen lesen Sie den HTML Microtask-Leitfaden. Wenn die Job-Warteschlange leer ist, wartet der Agent darauf, dass weitere Jobs hinzugefügt werden.
"Run-to-completion"
Jeder Job wird vollständig verarbeitet, bevor ein anderer Job verarbeitet wird. Dies bietet einige angenehme Eigenschaften, wenn Sie über Ihr Programm nachdenken, einschließlich der Tatsache, dass immer, wenn eine Funktion läuft, sie nicht unterbrochen werden kann und vollständig läuft, bevor ein anderer Code läuft (und Daten, die die Funktion manipuliert, ändern kann). Dies unterscheidet sich von C, wo eine Funktion, die in einem Thread läuft, jederzeit vom Laufzeitsystem angehalten werden kann, um in einem anderen Thread einen anderen Code auszuführen.
Betrachten Sie zum Beispiel dieses Beispiel:
const promise = Promise.resolve();
let i = 0;
promise.then(() => {
i += 1;
console.log(i);
});
promise.then(() => {
i += 1;
console.log(i);
});
In diesem Beispiel erstellen wir ein bereits erfülltes Promise, was bedeutet, dass jeder angehängte Rückruf sofort als Jobs geplant wird. Die beiden Rückrufe scheinen ein Rennen zu verursachen, aber tatsächlich ist die Ausgabe vollständig vorhersehbar: 1 und 2 werden in der Reihenfolge ausgegeben. Dies liegt daran, dass jeder Job vollständig ausgeführt wird, bevor der nächste ausgeführt wird, sodass die Gesamtreihenfolge immer i += 1; console.log(i); i += 1; console.log(i); ist und nie i += 1; i += 1; console.log(i); console.log(i);.
Ein Nachteil dieses Modells ist, dass eine Webanwendung nicht in der Lage ist, Benutzerinteraktionen wie Klicken oder Scrollen zu verarbeiten, wenn ein Job zu lange dauert, um abgeschlossen zu werden. Der Browser mildert dies mit dem "Ein Skript braucht zu lange, um ausgeführt zu werden"-Dialog ab. Eine gute Praxis ist, die Verarbeitung von Jobs kurz zu halten und, wenn möglich, einen Job in mehrere Jobs zu unterteilen.
Niemals blockierend
Ein weiteres wichtiges Versprechen des Ereignisschleifenmodells ist, dass die JavaScript-Ausführung niemals blockiert. Die Behandlung von I/O wird in der Regel über Ereignisse und Rückrufe durchgeführt, sodass die Anwendung andere Dinge wie Benutzereingaben verarbeiten kann, während auf das Ergebnis einer IndexedDB-Abfrage oder eines fetch()-Anrufs gewartet wird. Der Code, der nach dem Abschluss einer asynchronen Aktion ausgeführt wird, wird immer als Rückruffunktion bereitgestellt (z. B. der Handler then(), die Rückruffunktion in setTimeout() oder der Ereignishandler), die einen Job definiert, der nach Abschluss der Aktion in die Job-Warteschlange aufgenommen wird.
Natürlich erfordert die Garantie "niemals blockierend" die Eigenasynchronität der Plattform-API, aber es gibt einige Legacy-Ausnahmen wie alert() oder das synchrone XHR. Es gilt als gute Praxis, sie zu vermeiden, um die Reaktionsfähigkeit der Anwendung sicherzustellen.
Agentencluster und Speicheraustausch
Mehrere Agenten können über Speicheraustausch kommunizieren und so einen Agentencluster bilden. Agenten befinden sich innerhalb desselben Clusters, wenn und nur wenn sie Speicher teilen können. Es gibt keine eingebaute Mechanismus für zwei Agentencluster, irgendwelche Informationen auszutauschen, daher können sie als vollständig isolierte Ausführungsmodelle betrachtet werden.
Beim Erstellen eines Agenten (z. B. durch das Starten eines Workers) gibt es einige Kriterien dafür, ob er im selben Cluster wie der aktuelle Agent ist oder ein neuer Cluster erstellt wird. Zum Beispiel sind die folgenden Paare von globalen Objekten jeweils innerhalb desselben Agentenclusters und können daher Speicher miteinander teilen:
- Ein
Window-Objekt und ein dedizierter Worker, den es erstellt hat. - Ein Worker (jeglicher Art) und ein dedizierter Worker, den es erstellt hat.
- Ein
Window-Objekt A und dasWindow-Objekt eines gleichursprünglicheniframe, das A erstellt hat. - Ein
Window-Objekt und ein gleichursprünglichesWindow-Objekt, das es geöffnet hat. - Ein
Window-Objekt und ein Worklet, das es erstellt hat.
Die folgenden Paare von globalen Objekten befinden sich nicht innerhalb desselben Agentenclusters und können daher keinen Speicher austauschen:
- Ein
Window-Objekt und ein gemeinsam genutzter Worker, den es erstellt hat. - Ein Worker (jeglicher Art) und ein gemeinsam genutzter Worker, den es erstellt hat.
- Ein
Window-Objekt und ein Service Worker, den es erstellt hat. - Ein
Window-Objekt A und dasWindow-Objekt einesiframe, das A erstellt hat, das nicht dieselbe Ursprungs-Domain wie A haben kann. - Zwei beliebige
Window-Objekte ohne Opener- oder Vorfahrenbeziehung. Dies trifft auch zu, wenn die beidenWindow-Objekte den gleichen Ursprung haben.
Für den genauen Algorithmus siehe die HTML-Spezifikation.
Agentenübergreifende Kommunikation und Speicherverwaltungsmodell
Wie bereits erwähnt, kommunizieren Agenten über den Speicheraustausch. Im Web wird der Speicher über die postMessage()-Methode geteilt. Der Leitfaden zur Verwendung von Web Workern bietet einen Überblick darüber. Normalerweise werden Daten nur nach Wert übergeben (über strukturierte Kopien), und daher sind keine Schwierigkeiten mit Parallelität verbunden. Um Speicher zu teilen, muss ein SharedArrayBuffer-Objekt gepostet werden, auf das mehrere Agenten gleichzeitig zugreifen können. Sobald zwei Agenten Zugriff auf denselben Speicher über einen SharedArrayBuffer haben, können sie Ausführungen über das Atomics-Objekt synchronisieren.
Es gibt zwei Möglichkeiten, auf gemeinsamen Speicher zuzugreifen: über normalen Speicherzugriff (der nicht atomar ist) und über atomaren Speicherzugriff. Letzterer ist sequenziell konsistent (was bedeutet, dass es eine strikte totale Ordnung von Ereignissen gibt, über die sich alle Agenten im Cluster einig sind), während der ersterer ungeordnet ist (was bedeutet, dass keine Ordnung existiert); JavaScript bietet keine Operationen mit anderen Ordnungszusicherungen an.
Die Spezifikation bietet die folgenden Richtlinien für Programmierer, die mit gemeinsamem Speicher arbeiten:
Wir empfehlen, Programme frei von Datenrennen zu halten, d.h. es soll unmöglich sein, dass es gleichzeitige nicht-atomare Operationen auf derselben Speicherstelle gibt. Datenrennfreie Programme haben Durchlaufsemantik, bei der jeder Schritt in der Auswertungssemantik eines jeden Agenten mit einander verwoben ist. Für datenrennfreie Programme ist es nicht notwendig, die Details des Speichermodells zu verstehen. Die Details sind wahrscheinlich nicht hilfreich, um Intuition zu entwickeln, die es einem ermöglicht, ECMAScript besser zu schreiben.
Allgemeiner gesagt, selbst wenn ein Programm nicht rennfrei ist, kann es vorhersehbares Verhalten haben, solange atomare Operationen nicht an irgendwelchen Datenrennen beteiligt sind und die Operationen, die rennen, alle die gleiche Zugriffgröße haben. Der einfachste Weg, Atomics nicht in Rennen zu verwickeln, ist sicherzustellen, dass unterschiedliche Speicherstellen von atomaren und nicht-atomaren Operationen verwendet werden und dass atomare Zugriffe unterschiedlicher Größe nicht gleichzeitig auf dieselben Stellen zugreifen. Effektiv sollte das Programm den gemeinsamen Speicher so stark typisiert wie möglich behandeln. Man kann sich dennoch nicht auf die Ordnung und das Timing nicht-atomarer Zugriffe, die rennen, verlassen, aber wenn Speicher stark typisiert behandelt wird, werden die rennenden Zugriffe nicht "reißen" (Teile ihrer Werte werden nicht gemischt).
Parallelverarbeitung und Sicherstellung des Fortschritts
Wenn mehrere Agenten zusammenarbeiten, gilt die niemals blockierende Garantie nicht immer. Ein Agent kann blockiert oder angehalten werden, während er darauf wartet, dass ein anderer Agent eine Aktion durchführt. Dies unterscheidet sich vom Warten auf ein Versprechen im selben Agenten, weil es den gesamten Agenten anhält und keinen anderen Code zwischenzeitlich ausführen lässt – mit anderen Worten, der Agent kann keinen Fortschritt machen.
Um Deadlocks zu vermeiden, gibt es einige starke Einschränkungen, wann und welche Agenten blockiert werden können.
- Jeder nicht blockierte Agent mit einem dedizierten Ausführungsthread macht schließlich Fortschritte.
- In einer Menge von Agenten, die einen Ausführungsthread teilen, macht schließlich ein Agent Fortschritte.
- Ein Agent verursacht keinen anderen Agenten, blockiert zu werden, außer über explizite APIs, die Blockierung bereitstellen.
- Nur bestimmte Agenten können blockiert sein. Im Web schließen diese dedizierte Worker und geteilte Worker ein, jedoch keine ähnliche Herkunftsfenster oder Service Worker.
Der Agentencluster stellt ein gewisses Maß an Integrität über die Aktivität seiner Agenten im Falle externer Pausen oder Beendigungen sicher:
- Ein Agent kann angehalten oder fortgesetzt werden, ohne sein Wissen oder seine Mitwirkung. Zum Beispiel kann das Navigieren von einem Fenster weg den Codeausführung anhalten, aber seinen Zustand beibehalten. Ein Agentencluster darf jedoch nicht teilweise deaktiviert werden, um zu verhindern, dass ein Agent verhungert, weil ein anderer Agent deaktiviert wurde. Zum Beispiel sind geteilte Worker niemals im selben Agentencluster wie das erstellende Fenster oder andere dedizierte Worker. Dies liegt daran, dass die Lebensdauer eines geteilten Workers unabhängig von Dokumenten ist: wenn ein Dokument deaktiviert wird, während sein dedizierter Worker eine Sperre hält, blockiert der geteilte Worker von der Sperre zu bekommen, bis der dedizierte Worker reaktiviert wird, wenn überhaupt. Währenddessen versuchen andere Worker von anderen Fenstern Zugriff auf den geteilten Worker und werden verhungert.
- Ebenso kann ein Agent durch Faktoren, die extern zum Cluster sind, beendet werden. Zum Beispiel Betriebssysteme oder Benutzer, die einen Browserprozess töten, oder der Browser, der einen Agenten aufgrund zu hoher Ressourcennutzung zwangsweise beendet. In diesem Fall werden alle Agenten im Cluster beendet. (Die Spezifikation erlaubt auch eine zweite Strategie, nämlich eine API, die es zumindest einem verbleibenden Mitglied des Clusters ermöglicht, die Beendigung und den Agenten zu identifizieren, der beendet wurde, aber dies ist im Web nicht implementiert.)
Spezifikationen
| Specification |
|---|
| ECMAScript® 2026 Language Specification> |
| ECMAScript® 2026 Language Specification> |
| HTML> |
Siehe auch
- Ereignisschleifen im HTML-Standard
- Was ist die Ereignisschleife? in den Node.js-Dokumenten