Shared Worker i JavaScript
Denne artikel forklarer Shared Worker
i JavaScript.
Vi vil forklare alt fra det grundlæggende ved Shared Worker
til praktiske brugsecases trin for trin.
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
Hvad er en Shared Worker
?
En Shared Worker
er en arbejdståd, der kan deles mellem flere sider (faner, vinduer, iframes osv.) inden for samme oprindelse. I modsætning til en side-specifik Dedicated Worker
er hovedfunktionen, at en enkelt baggrundsproces kan deles mellem flere sider. Typiske anvendelsestilfælde inkluderer følgende:.
- Du kan dele en enkelt WebSocket-forbindelse på tværs af flere faner, hvilket reducerer antallet af forbindelser og centraliserer genopkoblinger.
- Du kan synkronisere tilstand mellem faner (centraliseret styring af Pub/Sub eller data-lagre).
- Du kan serialisere IndexedDB-operationer og styre samtidig adgang.
- Du kan forhindre dobbelt udførsel af beregningstunge processer.
Shared Worker
har en anden rolle endService Worker
.Service Worker
fungerer primært som en netværksproxy, mensShared Worker
fokuserer på at udføre vilkårlige beregninger eller tilstandsstyring, der deles mellem sider.
Grundlæggende API og livscyklus
Oprettelse (hovedtråden)
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 });
- Denne kode viser, hvordan man opretter en
Shared Worker
og sender/modtager beskeder gennem dens port.
Modtager (inden for 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)
- Denne kode demonstrerer, hvordan en
MessagePort
modtages for hver forbindelse iShared Worker
, og behandler samt svarer på beskeder fra klienter.
Livscyklus for en Shared Worker
Shared Worker
starter, når den første forbindelse etableres og kan afslutte, når den sidste port lukkes. Forbindelser lukkes, når siden genindlæses eller lukkes, og worker'en genskabes om nødvendigt.
Shared Worker
er et "langvarigt script, der deles i browseren". Hvis du ikke er opmærksom på Shared Worker
-livscyklussen, er der større sandsynlighed for problemer som ressource-lækager, forældet tilstand og utilsigtede genstarter.
Prøv det: Broadcast mellem faner
Dette er en minimal implementering, hvor flere faner forbinder til den samme Shared Worker
, og enhver besked sendes videre til alle faner.
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});
- Denne kode forbinder til en
Shared Worker
, implementerer modtagelse/visning af beskeder, afsendelse via en formular, og sender et ping ved knaptryk.
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};
- Denne kode implementerer en simpel pub/sub-funktion i
Shared Worker
til videresendelse af beskeder mellem flere 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>
- Hvis du åbner denne side i flere faner, vil beskeder sendt fra en hvilken som helst fane blive vist til de andre.
Beskeddtekning: Request/Response og Correlation ID
Når flere klienter interagerer med en Shared Worker
, ønsker du ofte at identificere, hvilket svar der hører til hvilken forespørgsel. Derfor er det standardpraksis at inkludere et korrelations-ID for at knytte svar til forespørgsler.
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'
- Denne kode implementerer en simpel RPC-klient, som kan lave asynkrone metodekald til
Shared Worker
. Her henviser en RPC-server (Remote Procedure Call-server) til en server, der giver en mekanisme til at kalde funktioner og procedurer fra andre programmer eller processer. - I dette eksempel øges
id
blot, men du kan også bruge en UUID, en tilfældig streng eller kombinere et tidsstempel med en tæller for at lave en unik nøgle.
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};
- Denne kode implementerer en enkel RPC-server, der udfører behandling i henhold til
method
og sender resultatet tilbage sammen med anmodnings-ID'et.
Typiske anvendelsesmønstre
Der er flere praktiske anvendelsesmønstre for Shared Worker
, såsom følgende:.
Multiplexing af en enkelt WebSocket (delt mellem faner)
Hvis hver fane åbner sin egen WebSocket-forbindelse, vil det påvirke serverbelastningen og forbindelsesgrænsen. Placer kun én WebSocket i Shared Worker
, og lad hver fane sende/modtage beskeder via worker'en.
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}
- Denne kode implementerer processen med at sende/modtage beskeder til en WebSocket-server og modtage forbindelsesstatus 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};
-
Denne kode implementerer et system i
Shared Worker
til at dele én WebSocket-forbindelse, udsende forbindelsesstatus og modtaget data til alle klienter, mens hver klients udgående forespørgsler sendes via WebSocket til serveren. -
Ved at centralisere genopkoblingsstrategier og kø til ikke-afsendte beskeder i
Shared Worker
bliver adfærden konsekvent på tværs af alle faner.
IndexedDB-mægling (serialisering)
Når du tilgår den samme database fra flere faner, og vil undgå konflikter fra samtidige transaktioner eller ventetid på låse, kan du sætte dem i kø i Shared Worker
og behandle dem sekventielt.
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};
- Denne kode implementerer et system, hvor læse-/skriveoperationer til IndexedDB sættes i kø via
Shared Worker
, så adgang fra flere faner serialiseres for at undgå konflikter og låser.
Modularisering og bundlertips
Hvis du ønsker at skrive dit Shared Worker
-script som et ES-modul, kan adfærd og understøttelse variere afhængigt af miljøet. I praksis er det sikrere at vælge en af følgende:.
- Skriv i klassisk worker-format, og brug
importScripts()
til at indlæse afhængigheder om nødvendigt. - Brug bundlerens worker-indgangspunkt (som Vite / Webpack / esbuild osv.), og opret et separat bundle til
Shared Worker
under bygning.
Tips til fejlhåndtering, detektion af afbrydelse og robusthed
Overvej følgende punkter for fejlhåndtering og robusthed:.
-
Håndtering af afsendelse før forbindelse er oprettet Sæt beskeder, der ankommer før porten er klar, i kø.
-
Detektion af afbrydelse
MessagePort
har ikke en standardonclose
-håndtering. På hovedsiden skal du sende en{type: 'bye'}
-besked medport.postMessage
underbeforeunload
, eller på anden vis angive det tydeligt i protokollen for at sikre, at worker'en bliver ryddet op. -
Genopkobling Når en side genindlæses eller en fane genåbnes, oprettes en ny port. Forbered en initial synkroniseringsbesked (til at sende hele tilstanden på én gang).
-
Backpressure Ved tung broadcasting, skift til throttling/debouncing eller send snapshots.
-
Sikkerhed En
Shared Worker
deles grundlæggende inden for samme oprindelse. Hvis du placerer hemmeligheder i worker'en, bør du overveje at designe sideskuds-bekræftelse med tokens eller lignende midler på kaldesiden.
Sådan bruges Dedicated Worker
, Shared Worker
og Service Worker
korrekt
Hver type Worker har følgende egenskaber:.
-
Dedicated Worker
Dedicated Worker
er kun beregnet til brug af én enkelt side. Det muliggør en 1:1-adskillelse af beregninger fra brugergrænsefladen. -
Shared Worker
Shared Worker
kan deles mellem flere sider med samme oprindelse. Det er ideelt til kommunikation mellem faner og deling af én forbindelse. -
Service Worker
Service Worker
kan bruges til netværksproxy, caching, offlinefunktioner, push-beskeder og baggrundssynkronisering. Dens styrke er evnen til at opsnappe fetch-anmodninger.
Som tommelfingerregel: brug
Shared Worker
til 'informationsdeling og vilkårlig behandling mellem faner',Service Worker
til 'netværkskontrol', ogDedicated Worker
, hvis du blot vil aflaste tunge processer fra brugerfladen.
Almindelige faldgruber
Når du bruger en Shared Worker
, skal du være opmærksom på følgende punkter.
-
At glemme at kalde
start()
eller unødvendige kald Når du brugerport.addEventListener('message', ...)
, skal du kaldeport.start()
. Dette er unødvendigt, hvis du brugerport.onmessage = ...
. -
Ubegrænset broadcasting Belastningen stiger, efterhånden som antallet af faner øges. Du kan reducere belastningen ved at implementere differentieret levering eller abonnementsemner (filtrering efter emne).
-
Kopieringsomkostninger for objekter
postMessage
duplikerer dataene. Ved store datamængder bør du overveje at sende det som Transferable (f.eks.ArrayBuffer
) eller ved at bruge delt hukommelse (SharedArrayBuffer
) sammen medAtomics
. -
Levetid og re-initialisering
Shared Worker
kan afsluttes, når den sidste klient afbryder forbindelsen. Design korrekt initialisering til første forbindelser og genstarter for at undgå bivirkninger og fejl.
Sammendrag
Shared Worker
er et sted til at implementere langvarig brugerdefineret logik, der deles mellem flere sider.- Uddyb beskedkontrakten (typer/protokoller) og gør dit anmodnings/svar-design robust ved at tilføje korrelations-IDs.
- Det er ideelt til processer, der fungerer bedre, når de er samlet på tværs af alle faner, såsom multiplex af WebSocket-forbindelser eller serialisering af adgang til IndexedDB.
- Finjustering af detaljer som bundlere, typedefinitioner og genforbindelse kan forbedre vedligeholdelse og brugeroplevelse markant.
Du kan følge med i ovenstående artikel ved hjælp af Visual Studio Code på vores YouTube-kanal. Husk også at tjekke YouTube-kanalen.