Shared Worker i JavaScript
Den här artikeln förklarar Shared Worker
i JavaScript.
Vi förklarar allt från grunderna kring Shared Worker
till praktiska användningsfall steg för steg.
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 i JavaScript
Vad är en Shared Worker
?
En Shared Worker
är en arbetstråd som kan delas mellan flera sidor (flikar, fönster, iframes, etc.) inom samma origin. Till skillnad från en sid-specifik Dedicated Worker
är huvudfunktionen att en enda bakgrundsprocess kan delas mellan flera sidor. Typiska användningsområden inkluderar följande:.
- Du kan dela en enda WebSocket-anslutning mellan flera flikar, vilket minskar antalet anslutningar och centraliserar återanslutningar.
- Du kan synkronisera tillstånd mellan flikar (centraliserad hantering för Pub/Sub eller lagring).
- Du kan serialisera IndexedDB-operationer och därmed samordna samtidiga åtkomster.
- Du kan förhindra dubbelkörning av beräkningstunga processer.
Shared Worker
har en annan roll änService Worker
.Service Worker
fungerar främst som en nätverksproxy, medanShared Worker
fokuserar på att utföra godtyckliga beräkningar eller tillståndshantering som delas mellan sidor.
Grundläggande API och livscykel
Skapande (huvudtråd)
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 });
- Den här koden visar hur man skapar en
Shared Worker
och skickar/tar emot meddelanden via dess port.
Mottagare (inom Shared Worker
-området)
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)
- Den här koden visar hur en
MessagePort
tas emot för varje anslutning iShared Worker
, hur meddelanden från klienter hanteras och besvaras.
Livscykel för en Shared Worker
Shared Worker
startar när den första anslutningen upprättas och kan avslutas när den sista porten stängs. Anslutningarna stängs vid omladdning/stängning av sidan och workern återskapas vid behov.
Shared Worker
är ett "långlivat skript som delas inom webbläsaren". Om du inte är uppmärksam på Shared Worker
-livscykeln är det mer sannolikt att problem som resursläckor, kvarstående gammalt tillstånd och oavsiktliga omstarter uppstår.
Prova: Sändning mellan flikar
Detta är en minimal implementation där flera flikar ansluter till samma Shared Worker
och alla skickade meddelanden sänds till alla flikar.
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});
- Denna kod ansluter till en
Shared Worker
, implementerar mottagning/visning av meddelanden, sändande från ett formulär samt sänder en ping vid knapptryckning.
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};
- Den här koden implementerar en enkel pub/sub-funktion inuti
Shared Worker
för att vidarebefordra meddelanden mellan flera klienter.
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>
- Om du öppnar denna sida i flera flikar kommer meddelanden skickade från en flik att meddelas till de andra.
Meddelandedesign: Request/Response och Correlation ID
När flera klienter interagerar med en Shared Worker
vill du ofta veta vilket svar som hör till vilken förfrågan. Därför är det standardpraxis att inkludera ett korrelations-ID för att koppla svar till förfrågningar.
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'
- Denna kod implementerar en enkel RPC-klient som kan göra asynkrona metodanrop till
Shared Worker
. Här avser en RPC-server (Remote Procedure Call server) en server som tillhandahåller en mekanism för att anropa funktioner och procedurer från andra program eller processer. - I detta exempel ökas
id
helt enkelt, men du kan också använda en UUID, en slumpmässig sträng eller kombinera en tidsstämpel med en räknare för att skapa en unik nyckel.
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};
- Denna kod implementerar en enkel RPC-server som utför bearbetning enligt
method
och skickar tillbaka resultatet tillsammans med förfrågnings-ID:t.
Typiska användningsmönster
Det finns flera praktiska mönster vid användning av Shared Worker
, bland annat:.
Multiplexning av en enda WebSocket (delad mellan flikar)
Om varje flik öppnar sin egen WebSocket-anslutning kommer det att påverka serverbelastningen och anslutningsgränsen. Placera bara en WebSocket i Shared Worker
och låt varje flik skicka/ta emot meddelanden via workern.
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}
- Den här koden implementerar processen att skicka/ta emot meddelanden till en WebSocket-server och få notifieringar om anslutningsstatus via en
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};
-
Denna kod implementerar en mekanism inuti
Shared Worker
för att dela en enda WebSocket-anslutning, sända anslutningsstatus och mottagen data till alla klienter, samt skicka varje klients utgående förfrågningar till servern via WebSocket. -
Genom att centralisera återanslutningsstrategier och köhantering av osända meddelanden i
Shared Worker
blir beteendet konsekvent över alla flikar.
IndexedDB-mediering (serialisering)
När du använder samma databas från flera flikar och vill undvika konflikter från samtidiga transaktioner eller låsning, kan du köa dem i Shared Worker
och behandla dem i serie.
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};
- Den här koden implementerar en mekanism där läs-/skrivoperationer till IndexedDB köas via
Shared Worker
och serialiserar tillgången från flera flikar för att undvika konflikter och låskonflikter.
Modularisering och tips för byggverktyg
Om du vill skriva din Shared Worker
-script som en ES-modul kan beteende och stöd variera beroende på miljön. I praktiken är det säkrast att välja ett av följande:.
- Skriv i klassiskt worker-format och använd
importScripts()
för att ladda beroenden vid behov. - Använd worker entry-funktionen i ditt byggverktyg (som Vite / Webpack / esbuild osv.) och skapa en separat bundle för
Shared Worker
vid byggtillfället.
Tips för felhantering, bortkopplingsdetektion och robusthet
Tänk på följande punkter för felhantering och robusthet:.
-
Hantera sändningar innan anslutning är upprättad Köa meddelanden som kommer innan porten är redo.
-
Detektering av bortkoppling
MessagePort
har ingen standardiseradonclose
-hanterare. På huvudsidan, skicka ett{type: 'bye'}
-meddelande medport.postMessage
underbeforeunload
, eller ange annars tydligt i protokollet för att säkerställa att arbetaren städas upp. -
Återanslutning När en sida laddas om eller en flik öppnas på nytt, skapas en ny port. Förbered ett initialt synkroniseringsmeddelande (för att skicka hela tillståndet på en gång).
-
Backpressure (tillbakahållning av data) Vid kraftig sändning, byt till throttling/debouncing eller skicka snapshots.
-
Säkerhet En
Shared Worker
delas i grunden inom samma origin. Om du placerar hemligheter i workern, fundera på att utforma sidosäkerställning med tokens eller liknande för anropande sida.
Hur man använder Dedicated Worker
, Shared Worker
och Service Worker
på rätt sätt
Varje typ av Worker har följande egenskaper:.
-
Dedicated Worker
Dedicated Worker
är avsedd att endast användas av en enskild sida. Den möjliggör en 1:1-separering av beräkningar från användargränssnittet. -
Shared Worker
Shared Worker
kan delas mellan flera sidor med samma ursprung. Den är idealisk för kommunikation mellan flikar och delning av en enda anslutning. -
Service Worker
Service Worker
kan användas för nätverksproxy, cachning, offline-åtgärder, push-notiser och bakgrundssynkronisering. Dess styrka är möjligheten att avlyssna fetch-förfrågningar.
Som en tumregel: använd
Shared Worker
för 'Informationsdelning och godtycklig behandling mellan flikar',Service Worker
för 'nätverkskontroll', ochDedicated Worker
om du bara vill avlasta tung bearbetning från användargränssnittet.
Vanliga fallgropar
Vid användning av Shared Worker
bör du vara uppmärksam på följande punkter.
-
Att glömma att anropa
start()
eller onödiga anrop När du använderport.addEventListener('message', ...)
måste du anropaport.start()
. Detta är onödigt om du använderport.onmessage = ...
. -
Obegränsad sändning Belastningen ökar när antalet flikar ökar. Du kan minska belastningen genom att implementera differentierad leverans eller prenumerationsämnen (filtrering per ämne).
-
Kostnad för kopiering av objekt
postMessage
duplicerar datan. För stora datamängder, överväg att skicka dem som Transferable (somArrayBuffer
) eller använda delat minne (SharedArrayBuffer
) tillsammans medAtomics
. -
Livslängd och återinitiering
Shared Worker
kan avslutas när den sista klienten kopplar från. Utforma initieringen ordentligt för första anslutningar och omstarter för att undvika bieffekter och buggar.
Sammanfattning
Shared Worker
är en plats för att implementera långlivad anpassad logik som delas mellan flera sidor.- Tydliggör meddelandekontraktet (typer/protokoll) och gör din begäran/svar-design robust genom att bifoga korrelations-ID:n.
- Den är idealisk för processer som fungerar bättre när de förenas över alla flikar, som multiplexing av WebSocket-anslutningar eller serialisering av åtkomst till IndexedDB.
- Att finslipa detaljer som bundlers, typedefinitioner och återanslutning kan avsevärt förbättra underhållbarhet och användarupplevelse.
Du kan följa med i artikeln ovan med hjälp av Visual Studio Code på vår YouTube-kanal. Vänligen kolla även in YouTube-kanalen.