| [ Web Proxy ] |
| Viewing: https://developer.mozilla.org/de/docs/Web/API/Web_Workers_API/Using_web_workers | [Back] [Original] |
Get to know MDN better
Dieser Inhalt wurde automatisch aus dem Englischen bersetzt, und kann Fehler enthalten. Erfahre mehr ber dieses Experiment.
Web Workers sind ein einfaches Mittel, um Webinhalte in Hintergrundthreads Skripte ausfhren zu lassen. Der Worker-Thread kann Aufgaben ausfhren, ohne die Benutzeroberflche zu beeintrchtigen. Zudem knnen sie Netzwerkanfragen ber die APIs fetch() oder XMLHttpRequest stellen. Sobald ein Worker erstellt wurde, kann er Nachrichten an den JavaScript-Code senden, der ihn erstellt hat, indem er Nachrichten an einen vom Code angegebenen Ereignishandler sendet (und umgekehrt).
Dieser Artikel bietet eine detaillierte Einfhrung in die Verwendung von Web Workers.
Ein Worker ist ein Objekt, das mithilfe eines Konstruktors erstellt wird (z.B. Worker()), das eine benannte JavaScript-Datei ausfhrt diese Datei enthlt den Code, der im Worker-Thread ausgefhrt wird; Worker laufen in einem anderen globalen Kontext, der sich vom aktuellen window unterscheidet. Daher fhrt die Verwendung der Abkrzung window, um den aktuellen globalen Bereich (anstatt self) in einem Worker zu erhalten, zu einem Fehler.
Der Worker-Kontext wird durch ein DedicatedWorkerGlobalScope-Objekt im Fall von Dedicated Workers dargestellt (standardmige Worker, die von einem einzigen Skript verwendet werden; Shared Workers verwenden SharedWorkerGlobalScope). Ein dedizierter Worker ist nur von dem Skript zugnglich, das ihn zuerst erzeugt hat, whrend Shared Workers von mehreren Skripten aus zugnglich sind.
Hinweis: Siehe Die Web Workers API bersichtsseite fr Referenzdokumentation ber Worker und zustzliche Leitfden.
Sie knnen im Worker-Thread beliebigen Code ausfhren, mit einigen Ausnahmen. Zum Beispiel knnen Sie nicht direkt den DOM von innerhalb eines Workers manipulieren oder einige Standardmethoden und -eigenschaften des window-Objekts verwenden. Aber Sie knnen viele der Elemente verwenden, die unter window verfgbar sind, einschlielich WebSockets und Datenspeichermechanismen wie IndexedDB. Sehen Sie sich Funktionen und Klassen, die in Workern verfgbar sind fr weitere Details an.
Daten werden zwischen Workern und dem Haupt-Thread ber ein Nachrichtensystem gesendet beide Seiten senden ihre Nachrichten mit der postMessage()-Methode und reagieren auf Nachrichten ber den onmessage-Ereignishandler (die Nachricht ist im Datenattribut des message-Ereignisses enthalten). Die Daten werden kopiert, anstatt geteilt zu werden.
Worker knnen wiederum neue Worker erzeugen, solange diese Worker innerhalb des gleichen Ursprungs wie die bergeordnete Seite gehostet werden.
Darber hinaus knnen Worker Netzwerkabfragen ber die APIs fetch() oder XMLHttpRequest durchfhren (beachten Sie jedoch, dass das responseXML-Attribut von XMLHttpRequest immer null sein wird).
Wie oben erwhnt, ist ein dedizierter Worker nur durch das Skript zugnglich, das ihn aufgerufen hat. In diesem Abschnitt werden wir den in unserem Grundlegenden dedizierten Worker-Beispiel gefundenen JavaScript-Code diskutieren (dedizierten Worker ausfhren): Dies ermglicht es Ihnen, zwei Zahlen einzugeben, die miteinander multipliziert werden sollen. Die Zahlen werden an einen dedizierten Worker gesendet, miteinander multipliziert und das Ergebnis wird an die Seite zurckgegeben und angezeigt.
Dieses Beispiel ist ziemlich trivial, wir haben jedoch entschieden, es einfach zu halten, whrend wir Ihnen grundlegende Worker-Konzepte vorstellen. Weitere fortgeschrittene Details werden spter im Artikel behandelt.
Fr eine etwas kontrolliertere Fehlerbehandlung und Abwrtskompatibilitt ist es eine gute Idee, Ihren Worker-Zugangscode in Folgendes einzuwickeln (main.js):
if (window.Worker) {
//
}
Einen neuen Worker zu erstellen ist einfach. Alles, was Sie tun mssen, ist den Worker()-Konstruktor aufzurufen und dabei die URI eines Skripts anzugeben, das im Worker-Thread ausgefhrt werden soll (main.js):
const myWorker = new Worker("worker.js");
Hinweis:
Bundler, einschlielich webpack, Vite und Parcel, empfehlen, URLs zu bergeben, die relativ zu import.meta.url aufgelst werden, an den Worker()-Konstruktor. Zum Beispiel:
const myWorker = new Worker(new URL("worker.js", import.meta.url));
Auf diese Weise ist der Pfad relativ zu dem aktuellen Skript anstatt zu der aktuellen HTML-Seite, was es dem Bundler ermglicht, sicher Optimierungen wie das Umbenennen durchzufhren (weil andernfalls die worker.js-URL auf eine Datei zeigen knnte, die nicht vom Bundler kontrolliert wird, so dass er keine Annahmen treffen kann).
Der Zauber von Workern geschieht durch die Methode postMessage() und den Ereignishandler onmessage. Wenn Sie eine Nachricht an den Worker senden mchten, senden Sie ihm wie folgt Nachrichten (main.js):
[first, second].forEach((input) => {
input.onchange = () => {
myWorker.postMessage([first.value, second.value]);
console.log("Message posted to worker");
};
});
Hier haben wir zwei <input>-Elemente, die durch die Variablen first und second dargestellt werden; wenn der Wert eines dieser Elemente gendert wird, wird myWorker.postMessage([first.value,second.value]) verwendet, um den Wert in beiden an den Worker zu senden, als Array. Sie knnen fast alles, was Sie mchten, in der Nachricht senden.
Im Worker knnen wir reagieren, wenn die Nachricht empfangen wird, indem wir einen Ereignisblock wie diesen schreiben (worker.js):
onmessage = (e) => {
console.log("Message received from main script");
const workerResult = `Result: ${e.data[0] * e.data[1]}`;
console.log("Posting message back to main script");
postMessage(workerResult);
};
Der onmessage-Handler ermglicht es uns, einige Codes auszufhren, wann immer eine Nachricht empfangen wird, wobei die Nachricht selbst im data-Attribut des message-Ereignisses verfgbar ist. Hier multiplizieren wir die beiden Zahlen miteinander und verwenden dann erneut postMessage(), um das Ergebnis zurck an den Haupt-Thread zu senden.
Zurck im Haupt-Thread verwenden wir erneut onmessage, um auf die Nachricht zu reagieren, die vom Worker zurckgesendet wurde:
myWorker.onmessage = (e) => {
result.textContent = e.data;
console.log("Message received from worker");
};
Hier erfassen wir die Nachrichtendaten und setzen sie als textContent des Ergebnis-Absatzes, sodass der Benutzer das Ergebnis der Berechnung sehen kann.
Hinweis:
Beachten Sie, dass onmessage und postMessage() an dem Worker-Objekt aufgehngt werden mssen, wenn sie im Hauptskript-Thread verwendet werden, aber nicht, wenn sie im Worker verwendet werden. Dies liegt daran, dass der Worker im Worker im Wesentlichen der globale Bereich ist.
Hinweis: Wenn eine Nachricht zwischen dem Haupt-Thread und dem Worker bergeben wird, wird sie kopiert oder "bertragen" (verschoben), nicht geteilt. Lesen Sie bertragen von Daten zu und von Workern: weitere Details fr eine weitaus grndlichere Erklrung.
Wenn Sie einen laufenden Worker sofort aus dem Haupt-Thread heraus beenden mssen, knnen Sie dies tun, indem Sie die terminate-Methode des Workers aufrufen:
myWorker.terminate();
Der Worker-Thread wird sofort beendet.
Wenn ein Laufzeitfehler im Worker auftritt, wird dessen onerror-Ereignishandler aufgerufen. Er erhlt ein Ereignis namens error, das das ErrorEvent-Interface implementiert.
Das Ereignis bubbelnd nicht und ist abbrechbar; um zu verhindern, dass die Standardeinstellung stattfindet, kann der Worker die Methode preventDefault() des Error-Ereignisses aufrufen.
Das Error-Ereignis hat die folgenden drei Felder, die von Interesse sind:
messageEine menschenlesbare Fehlermeldung.
filenameDer Name der Skriptdatei, in der der Fehler aufgetreten ist.
linenoDie Zeilennummer der Skriptdatei, in der der Fehler aufgetreten ist.
Worker knnen weitere Worker erzeugen, wenn sie mchten. So genannte Subworker mssen innerhalb desselben Ursprung wie die bergeordnete Seite gehostet werden. Auch die URIs fr Subworker werden relativ zum Standort des bergeordneten Workers aufgelst und nicht zum Standort der besitzenden Seite. Dies erleichtert es Workern, den berblick darber zu behalten, wo sich ihre Abhngigkeiten befinden.
Worker-Threads haben Zugriff auf eine globale Funktion, importScripts(), die es ihnen ermglicht, Skripte zu importieren. Sie akzeptiert null oder mehr URIs als Parameter zu den Ressourcen, die importiert werden sollen; alle folgenden Beispiele sind gltig:
importScripts(); /* imports nothing */
importScripts("foo.js"); /* imports just "foo.js" */
importScripts("foo.js", "bar.js"); /* imports two scripts */
importScripts(
"//example.com/hello.js",
); /* You can import scripts from other origins */
Der Browser ldt jedes aufgelistete Skript und fhrt es aus. Alle globalen Objekte aus jedem Skript knnen dann vom Worker verwendet werden. Wenn das Skript nicht geladen werden kann, wird ein NETWORK_ERROR ausgelst, und der nachfolgende Code wird nicht mehr ausgefhrt. Zuvor ausgefhrter Code (einschlielich Code, der mit setTimeout() verzgert wurde) bleibt jedoch funktionsfhig. Funktionsdeklarationen nach der importScripts()-Methode werden ebenfalls beibehalten, da diese immer vor dem Rest des Codes ausgewertet werden.
Hinweis:
Skripte knnen in beliebiger Reihenfolge heruntergeladen werden, werden jedoch in der Reihenfolge ausgefhrt, in der Sie die Dateinamen in importScripts() bergeben. Dies geschieht synchron; importScripts() gibt erst dann zurck, wenn alle Skripte geladen und ausgefhrt wurden.
Ein Shared Worker ist von mehreren Skripten aus zugnglich selbst wenn sie von verschiedenen Fenstern, Iframes oder sogar Workern aufgerufen werden. In diesem Abschnitt werden wir den in unserem Grundlegenden Shared Worker-Beispiel gefundenen JavaScript-Code diskutieren (Shared Worker ausfhren): Dies ist sehr hnlich dem einfachen dedizierten Worker-Beispiel, auer dass es zwei verfgbare Funktionen gibt, die von verschiedenen Skriptdateien verarbeitet werden: zwei Zahlen multiplizieren oder eine Zahl quadrieren. Beide Skripte verwenden denselben Worker, um die erforderliche Berechnung durchzufhren.
Hier konzentrieren wir uns auf die Unterschiede zwischen dedizierten und Shared Workern. Beachten Sie, dass wir in diesem Beispiel zwei HTML-Seiten mit JavaScript haben, das den gleichen einzelnen Worker verwendet.
Hinweis: Wenn auf SharedWorker von mehreren Browserkontexten aus zugegriffen werden kann, mssen alle diese Browserkontexte denselben Ursprung teilen (gleiches Protokoll, Host und Port).
Hinweis: In Firefox knnen Shared Workers nicht zwischen Dokumenten, die in privaten und nicht privaten Fenstern geladen sind, geteilt werden (Firefox Bug 1177621).
Einen neuen Shared Worker zu erzeugen, ist fast dasselbe wie bei einem dedizierten Worker, aber mit einem anderen Konstruktornamen (siehe index.html und index2.html) jede muss den Worker mit einem solchen Code erzeugen:
const myWorker = new SharedWorker("worker.js");
Ein groer Unterschied ist, dass Sie bei einem Shared Worker ber ein port-Objekt kommunizieren mssen ein expliziter Port wird geffnet, den die Skripte zur Kommunikation mit dem Worker verwenden knnen (dies geschieht implizit im Fall dedizierter Worker).
Die Portverbindung muss entweder implizit durch die Nutzung des onmessage-Ereignishandlers oder explizit mit der start()-Methode gestartet werden, bevor Nachrichten gepostet werden knnen. start() zu rufen ist nur notwendig, wenn das message-Ereignis ber die addEventListener()-Methode angeschlossen ist.
Hinweis:
Beim Aufruf der start()-Methode zum ffnen der Portverbindung muss sie sowohl vom bergeordneten Thread als auch vom Worker-Thread gerufen werden, wenn eine zweiseitige Kommunikation bentigt wird.
Shared Workers werden geschlossen, wenn sie von keinen Fenstern, Iframes oder Workern mehr referenziert werden.
Browser knnten Worker am Leben halten zwischen gleichartigen Navigationsvorgngen, um die Kosten eines Neustarts des Shared Workers, der von einer Website verwendet wird, zu vermeiden, wenn der Benutzer sich innerhalb dieser Website von Seite zu Seite bewegt.
Die extendedLifetime-Konstruktoroption kann auch angegeben werden, um einen Shared Worker am Leben zu halten, kurz nachdem alle Referenzen an ihn geschlossen wurden:
const worker = new SharedWorker("worker.js", { extendedLifetime: true });
Dies ermglicht es, nachdem der Benutzer die Seite verlassen hat, Arbeiten wie das Schreiben von Zustandsinformationen in den Speicher oder das Senden von Analysedaten zurck zu Servern auszufhren. Dies ist ergonomischer als einen Service Worker fr denselben Zweck zu verwenden.
Jetzt knnen Nachrichten wie gewohnt an den Worker gesendet werden, aber die postMessage()-Methode muss durch das Portobjekt aufgerufen werden (erneut werden Sie hnliche Konstruktionen in sowohl multiply.js als auch square.js sehen):
squareNumber.onchange = () => {
myWorker.port.postMessage([squareNumber.value, squareNumber.value]);
console.log("Message posted to worker");
};
Nun zum Worker. Es gibt hier ebenfalls ein wenig mehr Komplexitt (worker.js):
onconnect = (e) => {
const port = e.ports[0];
port.onmessage = (e) => {
const workerResult = `Result: ${e.data[0] * e.data[1]}`;
port.postMessage(workerResult);
};
};
Zuerst verwenden wir einen onconnect-Handler, um Code auszulsen, wenn eine Verbindung zum Port hergestellt wird (d.h. wenn der onmessage-Ereignishandler im bergeordneten Thread eingerichtet oder die start()-Methode explizit im bergeordneten Thread aufgerufen wird).
Wir verwenden das ports-Attribut dieses Ereignisobjektes, um den Port zu erfassen und ihn in einer Variablen zu speichern.
Als nchstes fgen wir einen onmessage-Handler am Port hinzu, um die Berechnung durchzufhren und das Ergebnis an den Haupt-Thread zurckzugeben. Das Einrichten dieses onmessage-Handlers im Worker-Thread ffnet ebenfalls implizit die Portverbindung zurck zum bergeordneten Thread, sodass der Aufruf von port.start() eigentlich nicht ntig ist, wie oben erwhnt.
Schlielich behandeln wir im Hauptskript die Nachricht (erneut werden Sie hnliche Konstrukte in sowohl multiply.js als auch square.js sehen):
myWorker.port.onmessage = (e) => {
result2.textContent = e.data;
console.log("Message received from worker");
};
Wenn durch den Port eine Nachricht vom Worker zurckkommt, fgen wir das Berechnungsergebnis in den entsprechenden Ergebnisabsatz ein.
Das Worker-Interface erzeugt reale Betriebssystem-Threads, und bewusste Programmierer knnten besorgt sein, dass Konkurrenz "interessante" Effekte in Ihrem Code verursachen kann, wenn Sie nicht sorgfltig sind.
Da jedoch Web Worker sorgfltig kontrollierte Kommunikationspunkte mit anderen Threads haben, ist es tatschlich sehr schwer, Konkurrenzprobleme zu verursachen. Es gibt keinen Zugriff auf nicht threadsichere Komponenten oder das DOM. Und Sie mssen spezifische Daten in und aus einem Thread durch serialisierte Objekte bergeben. Daher mssen Sie wirklich hart arbeiten, um Probleme in Ihrem Code zu verursachen.
Worker gelten als eigenstndiger Ausfhrungskontext, der sich von dem Dokument unterscheidet, das sie erstellt hat. Deshalb werden sie im Allgemeinen nicht von der Content-Security-Richtlinie des Dokuments (oder des Eltern-Workers), das sie erstellt hat, geregelt. Angenommen, ein Dokument wird mit folgendem Header bereitgestellt:
Content-Security-Policy: script-src 'self'
Unter anderem wird dies verhindern, dass alle eingeschlossenen Skripte eval() verwenden. Wenn das Skript jedoch einen Worker erstellt, wird es dem Code, der im Kontext des Workers luft, erlaubt sein, eval() zu verwenden.
Um eine Content-Security-Richtlinie fr den Worker zu spezifizieren, legen Sie einen Content-Security-Policy-Antwortheader fr die Anfrage fest, welche das Worker-Skript selbst bereitstellt.
Die Ausnahme hiervon ist, wenn der Ursprung des Worker-Skriptes ein global einzigartiger Bezeichner ist (zum Beispiel, wenn seine URL ein Daten- oder Blob-Schema hat). In diesem Fall erbt der Worker die CSP des Dokuments oder des Workers, der ihn erstellt hat.
Daten, die zwischen der Hauptseite und Workern bergeben werden, werden kopiert, nicht geteilt (mit Ausnahme bestimmter Objekte, die explizit geteilt werden knnen). Objekte werden serialisiert, wenn sie an den Worker bergeben werden, und anschlieend auf der anderen Seite deserialisiert. Die Seite und der Worker teilen nicht dieselbe Instanz, sodass das Endergebnis eine Kopie auf jeder Seite ist. Die meisten Browser implementieren diese Funktion als strukturierte Klonen.
Wie Sie wahrscheinlich inzwischen wissen, werden Daten zwischen den beiden Threads ber Nachrichten mit postMessage() ausgetauscht, und das data-Attribut des message-Ereignisses enthlt die Daten, die vom Worker zurckgesendet werden.
example.html: (die Hauptseite):
const myWorker = new Worker("my_task.js");
myWorker.onmessage = (event) => {
console.log(`Worker said : "${event.data}"`);
};
myWorker.postMessage({ lastUpdate: new Date() });
my_task.js (der Worker):
self.onmessage = (event) => {
postMessage(`Last updated: ${event.data.lastUpdate.toDateString()}`);
};
Der strukturierte Klon-Algorithmus kann JSON und einige Dinge akzeptieren, die JSON nicht kann wie zirkulre Referenzen.
Wenn Sie komplexe Daten bergeben mssen und viele verschiedene Funktionen sowohl auf der Hauptseite als auch im Worker aufrufen mssen, knnen Sie ein System erstellen, das alles zusammenfasst.
Zuerst erstellen wir eine QueryableWorker-Klasse, die die URL des Workers, einen Standard-Listener und einen Fehler-Handler bernimmt, und diese Klasse wird eine Liste von Listenern berwachen und uns helfen, mit dem Worker zu kommunizieren:
function QueryableWorker(url, defaultListener, onError) {
const worker = new Worker(url);
const listeners = {};
this.defaultListener = defaultListener ?? (() => {});
if (onError) {
worker.onerror = onError;
}
this.postMessage = (message) => {
worker.postMessage(message);
};
this.terminate = () => {
worker.terminate();
};
}
Dann fgen wir die Methoden zum Hinzufgen/Entfernen von Listenern hinzu:
this.addListeners = (name, listener) => {
listeners[name] = listener;
};
this.removeListeners = (name) => {
delete listeners[name];
};
Hier lassen wir den Worker zwei einfache Operationen fr die Veranschaulichung behandeln: die Differenz von zwei Zahlen berechnen und eine Benachrichtigung nach drei Sekunden auslsen. Um dies zu erreichen, implementieren wir zuerst eine sendQuery-Methode, die prft, ob der Worker tatschlich ber die entsprechenden Methoden verfgt, um das zu tun, was wir wollen.
// This functions takes at least one argument, the method name we want to query.
// Then we can pass in the arguments that the method needs.
this.sendQuery = (queryMethod, ...queryMethodArguments) => {
if (!queryMethod) {
throw new TypeError(
"QueryableWorker.sendQuery takes at least one argument",
);
}
worker.postMessage({
queryMethod,
queryMethodArguments,
});
};
Wir beenden QueryableWorker mit der onmessage-Methode. Wenn der Worker die entsprechenden Methoden hat, die wir abgefragt haben, sollte er den Namen des entsprechenden Listeners und die bentigten Argumente zurckgeben. Wir mssen ihn nur in listeners finden:
worker.onmessage = (event) => {
if (
event.data instanceof Object &&
Object.hasOwn(event.data, "queryMethodListener") &&
Object.hasOwn(event.data, "queryMethodArguments")
) {
listeners[event.data.queryMethodListener].apply(
this,
event.data.queryMethodArguments,
);
} else {
this.defaultListener(event.data);
}
};
Jetzt zum Worker. Zuerst mssen wir die Methoden haben, um die zwei einfachen Operationen zu bearbeiten:
const queryableFunctions = {
getDifference(a, b) {
reply("printStuff", a - b);
},
waitSomeTime() {
setTimeout(() => {
reply("doAlert", 3, "seconds");
}, 3000);
},
};
function reply(queryMethodListener, ...queryMethodArguments) {
if (!queryMethodListener) {
throw new TypeError("reply - takes at least one argument");
}
postMessage({
queryMethodListener,
queryMethodArguments,
});
}
// This method is called when main page calls QueryWorker's postMessage
// method directly
function defaultReply(message) {
// do something
}
Und die onmessage-Methode ist jetzt trivial:
onmessage = (event) => {
if (
event.data instanceof Object &&
Object.hasOwn(event.data, "queryMethod") &&
Object.hasOwn(event.data, "queryMethodArguments")
) {
queryableFunctions[event.data.queryMethod].apply(
self,
event.data.queryMethodArguments,
);
} else {
defaultReply(event.data);
}
};
Hier ist die vollstndige Implementierung:
example.html (die Hauptseite):
<ul>
<li>
<button id="first-action">What is the difference between 5 and 3?</button>
</li>
<li>
<button id="second-action">Wait 3 seconds</button>
</li>
<li>
<button id="terminate">terminate() the Worker</button>
</li>
</ul>
Dies muss das folgende Skript ausfhren, entweder inline oder als externe Datei:
// QueryableWorker instances methods:
// * sendQuery(queryable function name, argument to pass 1, argument to pass 2, etc. etc.): calls a Worker's queryable function
// * postMessage(string or JSON Data): see Worker.prototype.postMessage()
// * terminate(): terminates the Worker
// * addListener(name, function): adds a listener
// * removeListener(name): removes a listener
// QueryableWorker instances properties:
// * defaultListener: the default listener executed only when the Worker calls the postMessage() function directly
function QueryableWorker(url, defaultListener, onError) {
const worker = new Worker(url);
const listeners = {};
this.defaultListener = defaultListener ?? (() => {});
if (onError) {
worker.onerror = onError;
}
this.postMessage = (message) => {
worker.postMessage(message);
};
this.terminate = () => {
worker.terminate();
};
this.addListener = (name, listener) => {
listeners[name] = listener;
};
this.removeListener = (name) => {
delete listeners[name];
};
// This functions takes at least one argument, the method name we want to query.
// Then we can pass in the arguments that the method needs.
this.sendQuery = (queryMethod, ...queryMethodArguments) => {
if (!queryMethod) {
throw new TypeError(
"QueryableWorker.sendQuery takes at least one argument",
);
}
worker.postMessage({
queryMethod,
queryMethodArguments,
});
};
worker.onmessage = (event) => {
if (
event.data instanceof Object &&
Object.hasOwn(event.data, "queryMethodListener") &&
Object.hasOwn(event.data, "queryMethodArguments")
) {
listeners[event.data.queryMethodListener].apply(
this,
event.data.queryMethodArguments,
);
} else {
this.defaultListener(event.data);
}
};
}
// your custom "queryable" worker
const myTask = new QueryableWorker("my_task.js");
// your custom "listeners"
myTask.addListener("printStuff", (result) => {
document
.getElementById("firstLink")
.parentNode.appendChild(
document.createTextNode(`The difference is ${result}!`),
);
});
myTask.addListener("doAlert", (time, unit) => {
alert(`Worker waited for ${time} ${unit} :-)`);
});
document.getElementById("first-action").addEventListener("click", () => {
myTask.sendQuery("getDifference", 5, 3);
});
document.getElementById("second-action").addEventListener("click", () => {
myTask.sendQuery("waitSomeTime");
});
document.getElementById("terminate").addEventListener("click", () => {
myTask.terminate();
});
my_task.js (der Worker):
const queryableFunctions = {
// example #1: get the difference between two numbers:
getDifference(minuend, subtrahend) {
reply("printStuff", minuend - subtrahend);
},
// example #2: wait three seconds
waitSomeTime() {
setTimeout(() => {
reply("doAlert", 3, "seconds");
}, 3000);
},
};
// system functions
function defaultReply(message) {
// your default PUBLIC function executed only when main page calls the queryableWorker.postMessage() method directly
// do something
}
function reply(queryMethodListener, ...queryMethodArguments) {
if (!queryMethodListener) {
throw new TypeError("reply - not enough arguments");
}
postMessage({
queryMethodListener,
queryMethodArguments,
});
}
onmessage = (event) => {
if (
event.data instanceof Object &&
Object.hasOwn(event.data, "queryMethod") &&
Object.hasOwn(event.data, "queryMethodArguments")
) {
queryableFunctions[event.data.queryMethod].apply(
self,
event.data.queryMethodArguments,
);
} else {
defaultReply(event.data);
}
};
Es ist mglich, den Inhalt jeder Nachricht von Hauptseite -> Worker und Worker -> Hauptseite umzuschalten. Und die Eigenschaftsnamen "queryMethod", "queryMethodListeners", "queryMethodArguments" knnen alles sein, solange sie in QueryableWorker und dem worker bereinstimmen.
Moderne Browser enthalten eine zustzliche Mglichkeit, bestimmte Objekttypen zu oder von einem Worker mit hoher Leistung zu bertragen. Transferable Objects werden mit einer Zero-Copy-Operation von einem Kontext in einen anderen bertragen, was zu einer erheblichen Leistungssteigerung beim Senden groer Datenstze fhrt.
Zum Beispiel, wenn ein ArrayBuffer von Ihrer Hauptanwendung an ein Worker-Skript bertragen wird, wird der originale ArrayBuffer gelscht und nicht mehr verwendbar. Sein Inhalt wird (buchstblich) in den Worker-Kontext bertragen.
// Create a 32MB "file" and fill it with consecutive values from 0 to 255 32MB = 1024 * 1024 * 32
const uInt8Array = new Uint8Array(1024 * 1024 * 32).map((v, i) => i);
worker.postMessage(uInt8Array.buffer, [uInt8Array.buffer]);
Das SharedArrayBuffer-Objekt ermglicht es zwei Threads, wie dem Worker und dem Haupt-Thread, gleichzeitig im selben Speicherbereich zu arbeiten und Daten auszutauschen, ohne ber den Nachrichtenmechanismus zu gehen. Die Verwendung von gemeinsamem Speicher bringt erhebliche Determinismus-, Sicherheits- und Leistungsbedenken mit sich, von denen einige im Artikel ber das JavaScript-Ausfhrungsmodell beschrieben werden.
Es gibt keine offizielle Mglichkeit, den Code eines Workers innerhalb einer Webseite einzubetten, wie es <script>-Elemente fr normale Skripte tun. Aber ein <script>-Element, das kein src-Attribut hat und ein type-Attribut hat, das keine ausfhrbare MIME-Art identifiziert, kann als Datenblockelement betrachtet werden, das JavaScript verwenden knnte. Datenblcke sind eine allgemeinere Funktion von HTML, die fast beliebige Textdaten tragen kann. So knnte ein Worker auf diese Weise eingebettet werden:
<!doctype html>
<html lang="en-US">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width" />
<title>MDN Example - Embedded worker</title>
<script type="text/js-worker">
// This script WON'T be parsed by JS engines because its MIME type is text/js-worker.
const myVar = "Hello World!";
// Rest of your worker code goes here.
</script>
<script>
// This script WILL be parsed by JS engines because its MIME type is text/javascript.
function pageLog(sMsg) {
// Use a fragment: browser will only render/reflow once.
const frag = document.createDocumentFragment();
frag.appendChild(document.createTextNode(sMsg));
frag.appendChild(document.createElement("br"));
document.querySelector("#logDisplay").appendChild(frag);
}
</script>
<script type="text/js-worker">
// This script WON'T be parsed by JS engines because its MIME type is text/js-worker.
onmessage = (event) => {
postMessage(myVar);
};
// Rest of your worker code goes here.
</script>
<script>
// This script WILL be parsed by JS engines because its MIME type is text/javascript.
// In the past blob builder existed, but now we use Blob
const blob = new Blob(
Array.prototype.map.call(
document.querySelectorAll("script[type='text/js-worker']"),
(script) => script.textContent,
),
{ type: "text/javascript" },
);
// Creating a new global "worker" variable from all our "text/js-worker" scripts.
const worker = new Worker(window.URL.createObjectURL(blob));
worker.onmessage = (event) => {
pageLog(`Received: ${event.data}`);
};
</script>
</head>
<body>
<div id="logDisplay"></div>
<script>
// Start the worker.
worker.postMessage("");
</script>
</body>
</html>
Der eingebettete Worker ist jetzt in einer neuen benutzerdefinierten document.worker-Eigenschaft verschachtelt.
Es sei auch darauf hingewiesen, dass Sie auch eine Funktion in ein Blob umwandeln und dann eine Objekt-URL aus diesem Blob generieren knnen. Zum Beispiel:
function fn2workerURL(fn) {
const blob = new Blob([`(${fn.toString()})()`], { type: "text/javascript" });
return URL.createObjectURL(blob);
}
Dieser Abschnitt bietet weitere Beispiele, wie man Web Worker verwendet.
Worker sind hauptschlich ntzlich, um Ihrem Code zu ermglichen, prozessorintensive Berechnungen auszufhren, ohne den Benutzeroberflchenthread zu blockieren. In diesem Beispiel wird ein Worker verwendet, um Fibonacci-Zahlen zu berechnen.
Der folgende JavaScript-Code wird in der in dem nchsten Abschnitt referenzierten "fibonacci.js" Datei gespeichert.
self.onmessage = (event) => {
const userNum = Number(event.data);
self.postMessage(fibonacci(userNum));
};
function fibonacci(num) {
let a = 1;
let b = 0;
while (num > 0) {
[a, b] = [a + b, a];
num--;
}
return b;
}
Der Worker setzt die Eigenschaft onmessage auf eine Funktion, die Nachrichten empfngt, die gesendet werden, wenn die postMessage()-Methode des Worker-Objekts aufgerufen wird. Dies fhrt die Berechnungen durch und gibt schlielich das Ergebnis an den Haupt-Thread zurck.
<form>
<div>
<label for="number">
Enter a number that is a zero-based index position in the fibonacci
sequence to see what number is in that position. For example, enter 6 and
you'll get a result of 8 the fibonacci number at index position 6 is 8.
</label>
<input type="number" id="number" />
</div>
<div>
<input type="submit" />
</div>
</form>
<p id="result"></p>
Dies muss das folgende Skript ausfhren, entweder inline oder als externe Datei:
const form = document.querySelector("form");
const input = document.querySelector('input[type="number"]');
const result = document.querySelector("p#result");
const worker = new Worker("fibonacci.js");
worker.onmessage = (event) => {
result.textContent = event.data;
console.log(`Got: ${event.data}`);
};
worker.onerror = (error) => {
console.log(`Worker error: ${error.message}`);
throw error;
};
form.onsubmit = (e) => {
e.preventDefault();
worker.postMessage(input.value);
input.value = "";
};
Die Webseite erzeugt ein <p>-Element mit der ID result, das verwendet wird, um das Ergebnis anzuzeigen, und erzeugt dann den Worker. Nach der Erzeugung des Workers wird der onmessage-Handler so konfiguriert, dass er die Ergebnisse durch Setzen des Inhalts des <p>-Elements anzeigt, und der onerror-Handler wird so eingerichtet, dass er die Fehlermeldung in der Entwicklerkonsole protokolliert.
Schlielich wird eine Nachricht an den Worker gesendet, um ihn zu starten.
Versuchen Sie dieses Beispiel live.
Da Multicore-Computer zunehmend verbreitet werden, ist es oft ntzlich, rechnerisch komplexe Aufgaben auf mehrere Worker aufzuteilen, die diese Aufgaben dann auf mehreren Prozessorkernen ausfhren knnen.
Neben dedizierten und Shared Web Workern gibt es andere Arten von Workern:
Die meisten Browser ermglichen es, Web Worker in ihren JavaScript-Debuggern genauso wie den Hauptthread zu debuggen! Sowohl Firefox als auch Chrome listen zum Beispiel JavaScript-Quelldateien sowohl fr den Haupt-Thread als auch fr aktive Worker-Threads auf, und alle diese Dateien knnen geffnet werden, um Haltepunkte und Protokollpunkte zu setzen.
Um zu lernen, wie man Web Worker debuggt, siehe die Dokumentation fr die JavaScript-Debugger jedes Browsers:
Um Devtools fr Web Worker zu ffnen, knnen Sie die folgenden URLs verwenden:
edge://inspect/chrome://inspect/about:debugging#/runtime/this-firefoxDiese Seiten zeigen einen berblick ber alle Service Worker. Sie mssen den relevanten nach der URL finden und inspizieren klicken, um Devtools wie Konsole und Debugger fr diesen Worker zuzugreifen.
Sie knnen die meisten Standard-JavaScript-Funktionen in einem Web Worker verwenden, einschlielich:
Navigatorfetch()Array, Date, Math, und StringsetTimeout() und setInterval()Das Hauptschliche, was Sie in einem Worker nicht tun knnen, ist die direkte Beeinflussung der bergeordneten Seite. Dazu gehrt das Manipulieren des DOMs und die Verwendung von Objekten dieser Seite. Sie mssen dies indirekt tun, indem Sie eine Nachricht zurck an das Hauptskript ber DedicatedWorkerGlobalScope.postMessage() senden und nderungen im Ereignishandler vornehmen.
Hinweis: Sie knnen testen, ob eine Methode oder Schnittstelle fr Worker verfgbar ist, indem Sie den Worker-Spielplatz verwenden.
Hinweis: Eine vollstndige Liste der Funktionen, die Workern verfgbar sind, finden Sie unter Funktionen und Schnittstellen, die in Workern verfgbar sind.
| Spezifikation |
|---|
| HTML # workers |
Worker SchnittstelleSharedWorker SchnittstelleOffscreenCanvas SchnittstelleDer Bauplan fr ein besseres Internet.
Teile dieses Inhalts sind 19982026 von einzelnen mozilla.org-Mitwirkenden. Inhalte sind verfgbar unter einer Creative-Commons-Lizenz.
| Web Proxy Viewer | New URL | Original Page |