Shared Worker in JavaScript
Dieser Artikel erklärt den Shared Worker
in JavaScript.
Wir erklären alles vom Grundlegenden des Shared Worker
bis hin zu praktischen Anwendungsfällen Schritt für Schritt.
YouTube Video
javascript-shared-worker.html
1<!DOCTYPE html>
2<html lang="en">
3<head>
4 <meta charset="UTF-8">
5 <title>JavaScript & HTML</title>
6 <style>
7 * {
8 box-sizing: border-box;
9 }
10
11 body {
12 margin: 0;
13 padding: 1em;
14 padding-bottom: 10em;
15 font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
16 background-color: #f7f9fc;
17 color: #333;
18 line-height: 1.6;
19 }
20
21 .container {
22 max-width: 800px;
23 margin: 0 auto;
24 padding: 1em;
25 background-color: #ffffff;
26 border: 1px solid #ccc;
27 border-radius: 10px;
28 box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
29 }
30
31 .container-flex {
32 display: flex;
33 flex-wrap: wrap;
34 gap: 2em;
35 max-width: 1000px;
36 margin: 0 auto;
37 padding: 1em;
38 background-color: #ffffff;
39 border: 1px solid #ccc;
40 border-radius: 10px;
41 box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
42 }
43
44 .left-column, .right-column {
45 flex: 1 1 200px;
46 min-width: 200px;
47 }
48
49 h1, h2 {
50 font-size: 1.2rem;
51 color: #007bff;
52 margin-top: 0.5em;
53 margin-bottom: 0.5em;
54 border-left: 5px solid #007bff;
55 padding-left: 0.6em;
56 background-color: #e9f2ff;
57 }
58
59 button {
60 display: block;
61 margin: 1em auto;
62 padding: 0.75em 1.5em;
63 font-size: 1rem;
64 background-color: #007bff;
65 color: white;
66 border: none;
67 border-radius: 6px;
68 cursor: pointer;
69 transition: background-color 0.3s ease;
70 }
71
72 button:hover {
73 background-color: #0056b3;
74 }
75
76 #output {
77 margin-top: 1em;
78 background-color: #1e1e1e;
79 color: #0f0;
80 padding: 1em;
81 border-radius: 8px;
82 min-height: 200px;
83 font-family: Consolas, monospace;
84 font-size: 0.95rem;
85 overflow-y: auto;
86 white-space: pre-wrap;
87 }
88
89 .highlight {
90 outline: 3px solid #ffc107; /* yellow border */
91 background-color: #fff8e1; /* soft yellow background */
92 transition: background-color 0.3s ease, outline 0.3s ease;
93 }
94
95 .active {
96 background-color: #28a745; /* green background */
97 color: #fff;
98 box-shadow: 0 0 10px rgba(40, 167, 69, 0.5);
99 transition: background-color 0.3s ease, box-shadow 0.3s ease;
100 }
101 </style>
102</head>
103<body>
104 <div class="container">
105 <h1>JavaScript Console</h1>
106 <button id="executeBtn">Execute</button>
107 <div id="output"></div>
108 </div>
109
110 <script>
111 // Override console.log to display messages in the #output element
112 (function () {
113 // Override console.log
114 const originalLog = console.log;
115 console.log = function (...args) {
116 originalLog.apply(console, args);
117 const message = document.createElement('div');
118 message.textContent = args.map(String).join(' ');
119 output.appendChild(message);
120 };
121
122 // Override console.error
123 const originalError = console.error;
124 console.error = function (...args) {
125 originalError.apply(console, args);
126 const message = document.createElement('div');
127 message.textContent = args.map(String).join(' ');
128 message.style.color = 'red'; // Color error messages red
129 output.appendChild(message);
130 };
131 })();
132
133 document.getElementById('executeBtn').addEventListener('click', () => {
134 // Prevent multiple loads
135 if (document.getElementById('externalScript')) return;
136
137 const script = document.createElement('script');
138 script.src = 'javascript-shared-worker.js';
139 script.id = 'externalScript';
140 //script.onload = () => console.log('javascript-shared-worker.js loaded and executed.');
141 //script.onerror = () => console.log('Failed to load javascript-shared-worker.js.');
142 document.body.appendChild(script);
143 });
144 </script>
145</body>
146</html>
Shared Worker in JavaScript
Was ist ein Shared Worker
?
Ein Shared Worker
ist ein Worker-Thread, der zwischen mehreren Seiten (Tabs, Fenstern, iframes usw.) innerhalb derselben Origin geteilt werden kann. Im Gegensatz zu einem seiten-spezifischen Dedicated Worker
besteht das Hauptmerkmal darin, dass ein einziger Hintergrundprozess von mehreren Seiten gemeinsam genutzt werden kann. Typische Anwendungsfälle sind unter anderem:.
- Sie können eine einzelne WebSocket-Verbindung zwischen mehreren Tabs teilen, wodurch die Anzahl der Verbindungen reduziert und das erneute Verbinden zentralisiert wird.
- Sie können den Status zwischen Tabs synchronisieren (zentrale Verwaltung für Pub/Sub oder Stores).
- Sie können IndexedDB-Operationen serialisieren und gleichzeitigen Zugriff vermitteln.
- Sie können die doppelte Ausführung rechenintensiver Prozesse verhindern.
Shared Worker
hat eine andere Rolle alsService Worker
.Service Worker
fungiert hauptsächlich als Netzwerk-Proxy, während sich derShared Worker
darauf konzentriert, beliebige Berechnungen oder Zustandsverwaltung seitenübergreifend auszuführen.
Grundlegende API und Lebenszyklus
Erstellung (Hauptthread)
1// main.js
2// The second argument 'name' serves as an identifier to share the same SharedWorker
3// if both the URL and name are the same.
4const worker = new SharedWorker('/shared-worker.js', { name: 'app-core' });
5
6// Get the communication port (MessagePort) with this SharedWorker
7const port = worker.port;
8
9// Receive messages with onmessage, send messages with postMessage
10port.onmessage = (ev) => {
11 console.log('From SharedWorker:', JSON.stringify(ev.data));
12};
13
14// When using onmessage, start() is not required
15// (start() is needed when using addEventListener instead)
16port.postMessage({ type: 'hello', from: location.pathname });
- Dieser Code zeigt, wie ein
Shared Worker
erstellt und wie Nachrichten über dessen Port gesendet/empfangen werden.
Empfänger (im Gültigkeitsbereich des Shared Worker
)
1// shared-worker.js
2// SharedWorkerGlobalScope has an onconnect event,
3// and a MessagePort is provided for each connection
4const ports = new Set();
5
6onconnect = (e) => {
7 const port = e.ports[0];
8 ports.add(port);
9
10 port.onmessage = (ev) => {
11 // Log the received message and send a reply back to the sender
12 console.log('From page:', ev.data);
13 port.postMessage({ type: 'ack', received: ev.data });
14 };
15
16 // For handling explicit disconnection from the page (via MessagePort.close())
17 port.onmessageerror = (err) => {
18 console.error('Message error:', err);
19 };
20
21 // Greeting immediately after connection
22 port.postMessage({ type: 'welcome', clientCount: ports.size });
23};
24
25// Explicit termination of the SharedWorker is usually unnecessary
26// (It will be garbage collected when all ports are closed)
- Dieser Code zeigt, wie im
Shared Worker
für jede Verbindung einMessagePort
empfangen wird und Nachrichten von Clients verarbeitet und beantwortet werden.
Lebenszyklus eines Shared Worker
Shared Worker
startet, wenn die erste Verbindung hergestellt wird, und kann beendet werden, wenn der letzte Port geschlossen wird. Verbindungen werden beim Neuladen oder Schließen der Seite geschlossen, und der Worker wird bei Bedarf neu erstellt.
Shared Worker
ist ein „lang laufendes Skript, das im Browser gemeinsam genutzt wird“. Wenn Sie den Lebenszyklus des Shared Worker
nicht beachten, können Probleme wie Ressourcenlecks, veralteter Zustand und unbeabsichtigte Neustarts häufiger auftreten.
Probieren Sie es aus: Broadcast zwischen Tabs
Dies ist eine Minimalimplementierung, bei der mehrere Tabs denselben Shared Worker
nutzen und jede gesendete Nachricht an alle Tabs übertragen wird.
main.js
1const worker = new SharedWorker('/shared-worker.js', { name: 'bus' });
2const port = worker.port;
3
4port.onmessage = (ev) => {
5 const msg = ev.data;
6 if (msg.type === 'message') {
7 const li = document.createElement('li');
8 li.textContent = `[${new Date(msg.at).toLocaleTimeString()}] ${msg.payload}`;
9 document.querySelector('#messages').appendChild(li);
10 } else if (msg.type === 'ready') {
11 console.log(`Connected. clients=${msg.clients}`);
12 }
13};
14
15document.querySelector('#form').addEventListener('submit', (e) => {
16 e.preventDefault();
17 const input = document.querySelector('#text');
18 port.postMessage({ type: 'publish', payload: input.value });
19 input.value = '';
20});
21
22document.querySelector('#ping').addEventListener('click', () => {
23 port.postMessage({ type: 'ping' });
24});
- Dieser Code verbindet sich mit einem
Shared Worker
, implementiert den Nachrichteneingang/-anzeige, Senden über ein Formular sowie das Senden eines Pings durch Klicken auf einen Button.
shared-worker.js
1// Minimal pub/sub bus in a SharedWorker
2const subscribers = new Set();
3
4/** Broadcast to all connected ports */
5function broadcast(message) {
6 for (const port of subscribers) {
7 try {
8 port.postMessage(message);
9 } catch (e) {
10 // Unsubscribe if sending fails, just in case
11 subscribers.delete(port);
12 }
13 }
14}
15
16onconnect = (e) => {
17 const port = e.ports[0];
18 subscribers.add(port);
19
20 port.onmessage = (ev) => {
21 const msg = ev.data;
22 if (msg && msg.type === 'publish') {
23 broadcast({ type: 'message', payload: msg.payload, at: Date.now() });
24 } else if (msg && msg.type === 'ping') {
25 port.postMessage({ type: 'pong', at: Date.now() });
26 }
27 };
28
29 port.postMessage({ type: 'ready', clients: subscribers.size });
30};
- Dieser Code implementiert eine einfache Pub/Sub-Funktion im
Shared Worker
, um Nachrichten zwischen mehreren Clients weiterzuleiten.
index.html
1<!doctype html>
2<html>
3 <body>
4 <form id="form">
5 <input id="text" placeholder="say something" />
6 <button>Send</button>
7 </form>
8 <button id="ping">Ping</button>
9 <ul id="messages"></ul>
10 <script src="/main.js" type="module"></script>
11 </body>
12</html>
- Wenn Sie diese Seite in mehreren Tabs öffnen, werden Nachrichten, die von einem Tab gesendet werden, den anderen angezeigt.
Nachrichtendesign: Request/Response und Korrelations-ID
Wenn mehrere Clients mit einem Shared Worker
interagieren, möchten Sie häufig wissen, welche Antwort zu welcher Anfrage gehört. Daher ist es gängige Praxis, eine Korrelations-ID hinzuzufügen, um Antworten mit Anfragen zu verknüpfen.
main.js
1function createClient(workerUrl, name) {
2 const w = new SharedWorker(workerUrl, { name });
3 const port = w.port;
4 const pending = new Map(); // id -> {resolve,reject}
5 let nextId = 1;
6
7 port.onmessage = (ev) => {
8 const { id, ok, result, error } = ev.data || {};
9 if (!id || !pending.has(id)) return;
10 const { resolve, reject } = pending.get(id);
11 pending.delete(id);
12 ok ? resolve(result) : reject(new Error(error));
13 };
14
15 function call(method, params) {
16 const id = nextId++;
17 return new Promise((resolve, reject) => {
18 pending.set(id, { resolve, reject });
19 port.postMessage({ id, method, params });
20 });
21 }
22
23 return { call };
24}
25
26// usage
27const client = createClient('/shared-worker.js', 'rpc');
28client.call('add', { a: 2, b: 3 }).then(console.log); // -> 5
29client.call('sleep', { ms: 500 }).then(console.log); // -> 'done'
- Dieser Code implementiert einen einfachen RPC-Client, der asynchrone Methodenaufrufe an den
Shared Worker
senden kann. Hier bezeichnet ein RPC-Server (Remote Procedure Call Server) einen Server, der einen Mechanismus bereitstellt, um Funktionen und Prozeduren aus anderen Programmen oder Prozessen aufzurufen. - In diesem Beispiel wird die
id
einfach inkrementiert, aber man könnte auch eine UUID, eine zufällige Zeichenkette oder eine Kombination aus Zeitstempel und Zähler verwenden, um einen eindeutigen Schlüssel zu erzeugen.
shared-worker.js
1// A small request/response router with correlation ids
2onconnect = (e) => {
3 const port = e.ports[0];
4
5 port.onmessage = async (ev) => {
6 const { id, method, params } = ev.data || {};
7 try {
8 let result;
9 switch (method) {
10 case 'add':
11 result = (params?.a ?? 0) + (params?.b ?? 0);
12 break;
13 case 'sleep':
14 await new Promise(r => setTimeout(r, params?.ms ?? 0));
15 result = 'done';
16 break;
17 default:
18 throw new Error(`Unknown method: ${method}`);
19 }
20 port.postMessage({ id, ok: true, result });
21 } catch (err) {
22 port.postMessage({ id, ok: false, error: String(err) });
23 }
24 };
25};
- Dieser Code implementiert einen einfachen RPC-Server, der die Verarbeitung entsprechend der
method
ausführt und das Ergebnis zusammen mit der Anfrage-ID zurücksendet.
Typische Nutzungsmuster
Es gibt mehrere praktische Muster für die Nutzung von Shared Worker,
z. B.:.
Multiplexing einer einzigen WebSocket-Verbindung (geteilt über mehrere Tabs)
Wenn jeder Tab seine eigene WebSocket-Verbindung öffnet, wirkt sich das auf die Serverlast und das Verbindungslimit aus. Platzieren Sie nur eine WebSocket im Shared Worker
, und jeder Tab sendet/empfängt Nachrichten über den Worker.
main.js
1const w = new SharedWorker('/shared-worker.js', { name: 'ws' });
2const port = w.port;
3
4port.onmessage = (ev) => {
5 const msg = ev.data;
6 if (msg.type === 'data') {
7 console.log('Server says:', msg.payload);
8 } else if (msg.type === 'socket') {
9 console.log('WS state:', msg.state);
10 }
11};
12
13function send(payload) {
14 port.postMessage({ type: 'send', payload });
15}
- Dieser Code implementiert das Senden/Empfangen von Nachrichten an/zu einem WebSocket-Server sowie das Empfangen von Verbindungsstatus-Benachrichtigungen via
Shared Worker
.
shared-worker.js
1let ws;
2const ports = new Set();
3
4function ensureSocket() {
5 if (ws && (ws.readyState === WebSocket.OPEN || ws.readyState === WebSocket.CONNECTING)) {
6 return;
7 }
8 ws = new WebSocket('wss://example.com/stream');
9 ws.onopen = () => broadcast({ type: 'socket', state: 'open' });
10 ws.onclose = () => broadcast({ type: 'socket', state: 'closed' });
11 ws.onerror = (e) => broadcast({ type: 'socket', state: 'error', detail: String(e) });
12 ws.onmessage = (m) => broadcast({ type: 'data', payload: m.data });
13}
14
15function broadcast(msg) {
16 for (const p of ports) p.postMessage(msg);
17}
18
19onconnect = (e) => {
20 const port = e.ports[0];
21 ports.add(port);
22 ensureSocket();
23
24 port.onmessage = (ev) => {
25 const msg = ev.data;
26 if (msg?.type === 'send' && ws?.readyState === WebSocket.OPEN) {
27 ws.send(JSON.stringify(msg.payload));
28 }
29 };
30};
-
Dieser Code implementiert im
Shared Worker
einen Mechanismus, um eine einzelne WebSocket-Verbindung zu teilen, Verbindungsstatus und empfangene Daten an alle Clients zu senden und die ausgehenden Anfragen jedes Clients über die WebSocket an den Server zu leiten. -
Durch die Zentralisierung von Wiederverbindungsstrategien und nicht gesendeten Nachrichtenwarteschlangen im
Shared Worker
wird das Verhalten in allen Tabs vereinheitlicht.
IndexedDB-Vermittlung (Serialisierung)
Wenn Sie von mehreren Tabs aus auf dieselbe Datenbank zugreifen und Konflikte durch gleichzeitige Transaktionen oder Lock-Warteschlangen vermeiden möchten, können Sie die Anfragen im Shared Worker
sammeln und sie nacheinander abarbeiten.
1// db-worker.js (very simplified – error handling omitted)
2let db;
3async function openDB() {
4 if (db) return db;
5 db = await new Promise((resolve, reject) => {
6 const req = indexedDB.open('appdb', 1);
7 req.onupgradeneeded = () => req.result.createObjectStore('kv');
8 req.onsuccess = () => resolve(req.result);
9 req.onerror = () => reject(req.error);
10 });
11 return db;
12}
13
14async function put(key, value) {
15 const d = await openDB();
16 return new Promise((resolve, reject) => {
17 const tx = d.transaction('kv', 'readwrite');
18 tx.objectStore('kv').put(value, key);
19 tx.oncomplete = () => resolve(true);
20 tx.onerror = () => reject(tx.error);
21 });
22}
23
24async function get(key) {
25 const d = await openDB();
26 return new Promise((resolve, reject) => {
27 const tx = d.transaction('kv', 'readonly');
28 const req = tx.objectStore('kv').get(key);
29 req.onsuccess = () => resolve(req.result);
30 req.onerror = () => reject(req.error);
31 });
32}
33
34onconnect = (e) => {
35 const port = e.ports[0];
36 port.onmessage = async (ev) => {
37 const { id, op, key, value } = ev.data;
38 try {
39 const result = op === 'put' ? await put(key, value) : await get(key);
40 port.postMessage({ id, ok: true, result });
41 } catch (err) {
42 port.postMessage({ id, ok: false, error: String(err) });
43 }
44 };
45};
- Dieser Code implementiert einen Mechanismus, bei dem Lese-/Schreiboperationen an IndexedDB über den
Shared Worker
in eine Warteschlange gestellt werden, sodass der Zugriff von mehreren Tabs serialisiert wird, um Konflikte und Sperrkonkurrenz zu vermeiden.
Modularisierung und Bundler-Tipps
Wenn Sie Ihr Shared Worker
-Skript als ES-Modul schreiben möchten, können Verhalten und Unterstützung je nach Umgebung unterschiedlich sein. In der Praxis ist es sicherer, eine der folgenden Möglichkeiten zu wählen:.
- Schreiben Sie im klassischen Worker-Format und nutzen Sie
importScripts()
, um bei Bedarf Abhängigkeiten zu laden. - Verwenden Sie das Worker-Entry-Feature Ihres Bundlers (z. B. Vite / Webpack / esbuild etc.) und erstellen Sie beim Build einen separaten Bundle für den
Shared Worker
.
Tipps für Fehlerbehandlung, Erkennung von Verbindungsabbrüchen und Robustheit
Beachten Sie die folgenden Punkte für Fehlerbehandlung und Robustheit:.
-
Umgang mit Send-Anforderungen vor Verbindungsaufbau Stellen Sie Nachrichten, die ankommen, bevor der Port bereit ist, in eine Warteschlange.
-
Erkennung von Verbindungsabbrüchen
MessagePort
hat keinen standardisiertenonclose
-Handler. Auf der Hauptseite sollte währendbeforeunload
eine Nachricht{type: 'bye'}
mitport.postMessage
gesendet werden oder anderweitig im Protokoll klar angegeben werden, um sicherzustellen, dass der Worker aufgeräumt wird. -
Wiederverbindung Wenn eine Seite neu geladen oder ein Tab wieder geöffnet wird, wird ein neuer Port erstellt. Bereiten Sie eine anfängliche Synchronisationsnachricht vor (um den vollständigen Status auf einmal zu senden).
-
Backpressure Bei starker Broadcast-Nutzung wechseln Sie zu Throttling/Debouncing oder dem Versenden von Snapshots.
-
Sicherheit Ein
Shared Worker
wird grundsätzlich innerhalb derselben Origin geteilt. Wenn Sie Geheimnisse im Worker speichern, sollten Sie eine seitliche Verifikation mit Tokens oder ähnlichen Mechanismen auf der aufrufenden Seite vorsehen.
Wie man Dedicated Worker
, Shared Worker
und Service Worker
angemessen verwendet
Jeder Worker-Typ hat die folgenden Eigenschaften:.
-
Dedicated Worker
Dedicated Worker
ist ausschließlich für die Nutzung durch eine einzelne Seite vorgesehen. Er ermöglicht eine 1:1-Trennung der Berechnungen von der Benutzeroberfläche. -
Shared Worker
Shared Worker
kann von mehreren Seiten mit demselben Ursprung geteilt werden. Er ist ideal für die Kommunikation zwischen Tabs und das Teilen einer einzigen Verbindung. -
Service Worker
Service Worker
kann für Netzwerk-Proxying, Caching, Offline-Betrieb, Push-Benachrichtigungen und Hintergrundsynchronisation verwendet werden. Seine Stärke liegt in der Fähigkeit, Fetch-Anfragen abzufangen.
Als Faustregel gilt: Verwenden Sie
Shared Worker
für 'Informationsaustausch und beliebige Verarbeitung zwischen Tabs',Service Worker
für 'Netzwerksteuerung' undDedicated Worker
, wenn Sie nur schwere Berechnungen vom UI auslagern möchten.
Häufige Fallstricke
Beim Einsatz eines Shared Worker
sollten Sie auf folgende Punkte achten.
-
Vergessen,
start()
aufzurufen oder unnötige Aufrufe Wenn Sieport.addEventListener('message', ...)
verwenden, müssen Sieport.start()
aufrufen. Dies ist nicht erforderlich, wenn Sieport.onmessage = ...
verwenden. -
Unkontrolliertes Broadcasting Die Last steigt an, je mehr Tabs geöffnet sind. Sie können die Last verringern, indem Sie differenzierte Zustellung oder Abonnement-Themen (Themenfilter) implementieren.
-
Kopieraufwand für Objekte
postMessage
dupliziert die Daten. Für große Datenmengen sollten Sie diese als Transferable (wie etwaArrayBuffer
) versenden oder gemeinsamen Speicher (SharedArrayBuffer
) in Kombination mitAtomics
verwenden. -
Lebensdauer und Re-Initialisierung
Shared Worker
kann beendet werden, wenn der letzte Client die Verbindung trennt. Entwerfen Sie die Initialisierung für erste Verbindungen und Neustarts sorgfältig, um Nebenwirkungen und Fehler zu vermeiden.
Zusammenfassung
Shared Worker
ist ein Ort, um langlebige, benutzerdefinierte Logik, die über mehrere Seiten geteilt wird, zu implementieren.- Klären Sie den Nachrichtenvertrag (Typen/Protokolle) und machen Sie das Request/Response-Design robust, indem Sie Korrelations-IDs anhängen.
- Er ist ideal für Prozesse, die über alle Tabs hinweg vereinheitlicht besser funktionieren, wie z.B. Multiplexing von WebSocket-Verbindungen oder Serialisierung des Zugriffs auf IndexedDB.
- Feinabstimmung von Details wie Bundlern, Typdefinitionen und Wiederverbindung kann die Wartbarkeit und Benutzererfahrung erheblich verbessern.
Sie können den obigen Artikel mit Visual Studio Code auf unserem YouTube-Kanal verfolgen. Bitte schauen Sie sich auch den YouTube-Kanal an.