Delt Worker i JavaScript
Denne artikkelen forklarer Shared Worker i JavaScript.
Vi vil forklare alt fra grunnleggende om Shared Worker til praktiske brukstilfeller, trinn for trinn.
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>Delt Worker i JavaScript
Hva er en Shared Worker?
En Shared Worker er en arbeidsprosess som kan deles mellom flere sider (faner, vinduer, iframes, osv.) innenfor samme opprinnelse. I motsetning til en side-spesifikk Dedicated Worker, er hovedfunksjonen at en enkelt bakgrunnsprosess kan deles mellom flere sider. Typiske brukstilfeller inkluderer følgende:.
- Du kan dele én enkelt WebSocket-tilkobling mellom flere faner, noe som reduserer antall tilkoblinger og sentraliserer gjenkobling.
- Du kan synkronisere tilstand mellom faner (sentralisert administrasjon for Pub/Sub eller lagring).
- Du kan serialisere IndexedDB-operasjoner, og dermed håndtere samtidig tilgang.
- Du kan forhindre duplisert kjøring av ressurskrevende prosesser.
Shared Workerhar en annen rolle ennService Worker.Service Workerfungerer hovedsakelig som en nettverksproxy, mensShared Workerfokuserer på å utføre vilkårlige beregninger eller tilstandshåndtering delt mellom sider.
Grunnleggende API og livssyklus
Opprettelse (Hovedtrå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 });- Denne koden viser hvordan du oppretter en
Shared Workerog sender/mottar meldinger gjennom dens port.
Mottaker (innenfor 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 koden viser hvordan en
MessagePortmottas for hver tilkobling iShared Worker, og hvordan meldinger fra klienter behandles og besvares.
Livssyklus for en Shared Worker
Shared Worker starter når den første tilkoblingen er etablert og kan avsluttes når den siste porten er lukket. Tilkoblinger lukkes ved omlasting/lukking av siden, og arbeideren blir gjenopprettet om nødvendig.
Shared Worker er et "langlevet skript delt i nettleseren". Hvis du ikke er oppmerksom på livssyklusen til Shared Worker, er det mer sannsynlig at problemer som ressurslekkasjer, foreldet tilstand og utilsiktede omstarter kan oppstå.
Prøv: Broadcast mellom faner
Dette er en minimal implementasjon der flere faner kobler seg til samme Shared Worker og alle sendte meldinger kringkastes 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 koden kobler til en
Shared Worker, implementerer mottak/visning av meldinger, sending fra et skjema og sender en ping ved å klikke på en knapp.
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 koden implementerer enkel pub/sub-funksjonalitet inne i
Shared Workerfor å videresende meldinger mellom 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 åpner denne siden i flere faner, vil meldinger sendt fra en fane varsles til de andre.
Meldingsdesign: Forespørsel/svar og korrelasjons-ID
Når flere klienter samhandler med en Shared Worker, ønsker du ofte å vite hvilket svar som hører til hvilken forespørsel. Derfor er det standard praksis å inkludere en korrelasjons-ID for å knytte svar til forespørsler.
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 koden implementerer en enkel RPC-klient som kan utføre asynkrone metodekall til
Shared Worker. Her refererer en RPC-server (Remote Procedure Call-server) til en server som tilbyr en mekanisme for å kalle funksjoner og prosedyrer fra andre programmer eller prosesser. - I dette eksemplet blir
idrett og slett økt med én, men du kan også bruke en UUID, en tilfeldig streng, eller kombinere et tidsstempel med en teller for å lage en unik nøkkel.
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 koden implementerer en enkel RPC-server som utfører behandling basert på
methodog sender resultatet tilbake sammen med forespørsels-IDen.
Typiske bruksmønstre
Det finnes flere praktiske mønstre for bruk av Shared Worker, for eksempel:.
Multipleksing av én enkelt WebSocket (delt mellom faner)
Hvis hver fane åpner sin egen WebSocket-tilkobling, vil det påvirke serverbelastningen og tilkoblingsgrensen. Ha bare én WebSocket i Shared Worker, og la hver fane sende/motta meldinger via arbeidstråden.
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 koden implementerer prosess for sending/mottak av meldinger til en WebSocket-server og mottak av tilkoblingsstatusvarsler 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 koden implementerer en mekanisme inne i
Shared Workerfor å dele en enkelt WebSocket-tilkobling, kringkaste tilkoblingsstatus og mottatte data til alle klienter, samtidig som hver klients utgående forespørsler sendes til serveren via WebSocket. -
Ved å sentralisere gjentilkoblingsstrategier og kø for ikke-sendte meldinger i
Shared Worker, blir oppførselen konsekvent på tvers av alle faner.
IndexedDB-megling (serialisering)
Når du får tilgang til samme database fra flere faner og ønsket å unngå konflikter fra samtidige transaksjoner eller låseventing, kan du sette dem i kø i Shared Worker og behandle 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};- Denne koden implementerer en mekanisme hvor lese- og skriveoperasjoner til IndexedDB settes i kø via
Shared Worker, slik at tilgang fra flere faner serialiseres for å unngå konflikter og låse-problemer.
Modularisering og tips for bundlere
Hvis du vil skrive ditt Shared Worker-skript som et ES-modul, kan oppførsel og støtte variere mellom ulike miljøer. I praksis er det tryggere å velge ett av følgende:.
- Skriv i klassisk worker-format, og bruk
importScripts()for å laste inn avhengigheter om nødvendig. - Bruk worker entry-funksjonen i din bundler (for eksempel Vite / Webpack / esbuild osv.), og lag en egen pakke/bundle for
Shared Workerunder bygging.
Tips for feilhåndtering, diskonsekstdeteksjon og robusthet
Vurder følgende punkter for feilhåndtering og robusthet:.
-
Håndtering av sendinger før tilkoblingen er etablert Sett meldinger som kommer før porten er klar i kø.
-
Deteksjon av frakobling
MessagePorthar ikke en standardonclose-håndterer. På hovedsiden, send en{type: 'bye'}-melding medport.postMessageunderbeforeunload, eller angi tydelig i protokollen på annen måte for å sikre at arbeideren blir ryddet opp. -
Gjenkobling Når en side lastes om eller en fane åpnes på nytt opprettes en ny port. Forbered en initial synkroniseringsmelding (for å sende hele tilstanden på en gang).
-
Backpressure Ved mye kringkasting, bytt til throttling/debouncing eller sending av øyeblikksbilder.
-
Sikkerhet En
Shared Workerer fundamentalt delt innenfor samme opprinnelse. Hvis du legger hemmeligheter i workeren, bør du vurdere å lage sidesjekk/verifikasjon med tokens eller lignende på kallesiden.
Hvordan bruke Dedicated Worker, Shared Worker og Service Worker på riktig måte
Hver type Worker har følgende egenskaper:.
-
Dedicated WorkerDedicated Workerer ment for bruk av kun én enkelt side. Det muliggjør en 1:1-separasjon av beregninger fra brukergrensesnittet. -
Shared WorkerShared Workerkan deles mellom flere sider med samme opprinnelse. Den er ideell for kommunikasjon mellom faner og deling av én enkelt tilkobling. -
Service WorkerService Workerkan brukes for nettverksproxy, caching, offline-operasjoner, push-varsler og bakgrunnssynkronisering. Dens styrke er evnen til å avskjære fetch-forespørsler.
Som tommelfingerregel: bruk
Shared Workerfor 'informasjonsdeling og vilkårlig behandling mellom faner',Service Workerfor 'nettverkskontroll', ogDedicated Workerhvis du bare vil avlaste tung prosessering fra brukergrensesnittet.
Vanlige fallgruver
Når du bruker en Shared Worker, må du være oppmerksom på følgende punkter.
-
Å glemme å kalle
start()eller unødvendige kall Når du brukerport.addEventListener('message', ...), må du kalleport.start(). Dette er unødvendig hvis du brukerport.onmessage = .... -
Ubegrenset kringkasting Belastningen øker ettersom antall faner øker. Du kan redusere belastningen ved å implementere differensiert levering eller abonnementsemner (filtrering etter emne).
-
Kopieringskostnad for objekter
postMessagedupliserer dataene. For store datamengder, vurder å sende det som Transferable (for eksempelArrayBuffer) eller bruk delt minne (SharedArrayBuffer) sammen medAtomics. -
Levetid og re-initiering
Shared Workerkan avsluttes når den siste klienten kobler fra. Design initialiseringen riktig for første tilkoblinger og omstarter for å unngå bivirkninger og feil.
Sammendrag
Shared Workerer et sted for å implementere langvarig, tilpasset logikk delt mellom flere sider.- Klargjør meldingskontrakten (typer/protokoller) og gjør forespørsels-/svar-designet robust ved å legge til korrelasjons-IDer.
- Den er ideell for prosesser som fungerer bedre når de er samlet for alle faner, slik som multipleksing av WebSocket-tilkoblinger eller seriel tilgang til IndexedDB.
- Finjustering av detaljer som bundlere, typedefinisjoner og gjenkobling kan i stor grad forbedre vedlikeholdbarhet og brukeropplevelse.
Du kan følge med på artikkelen ovenfor ved å bruke Visual Studio Code på vår YouTube-kanal. Vennligst sjekk ut YouTube-kanalen.