Shared Worker sa JavaScript
Ipinaliwanag ng artikulong ito ang tungkol sa Shared Worker
sa JavaScript.
Ipapaliwanag namin mula sa mga pangunahing kaalaman ng Shared Worker
hanggang sa mga aktwal na paggamit, hakbang-hakbang.
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 sa JavaScript
Ano ang Shared Worker
?
Ang Shared Worker
ay isang worker thread na maaaring ipamahagi sa maraming pahina (mga tab, window, iframe, atbp.) sa loob ng parehong origin. Hindi tulad ng page-specific na Dedicated Worker
, ang pangunahing katangian nito ay isang background na proseso ang maaaring i-share o gamitin ng maraming pahina. Ilan sa mga karaniwang gamit nito ay ang mga sumusunod:.
- Maaring ipamahagi ang isang WebSocket connection sa maraming tab, kaya mababawasan ang bilang ng koneksyon at magiging sentralisado ang reconnection.
- Maaring i-synchronize ang estado sa pagitan ng mga tab (sentralisadong pamamahala para sa Pub/Sub o mga tindahan ng datos).
- Maaring i-serialize ang mga operasyon sa IndexedDB, upang mapamagitan ang sabay-sabay na access.
- Maaring maiwasan ang dobleng pagtakbo ng mga mabibigat na proseso.
Iba ang papel ng
Shared Worker
kumpara saService Worker
. Ang pangunahing papel ngService Worker
ay magsilbing network proxy, habang angShared Worker
ay nakatuon sa pagsasagawa ng iba't ibang kalkulasyon o pamamahala ng estado na ginagamit ng maraming pahina.
Pangunahing API at Lifecycle
Paglikha (Main Thread)
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 });
- Ipinapakita ng code na ito kung paano gumawa ng
Shared Worker
at magpadala/tumanggap ng mga mensahe sa pamamagitan ng kanyang port.
Receiver (sa loob ng saklaw ng 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)
- Ipinapakita ng code na ito kung paano, sa loob ng
Shared Worker
, tumatanggap ngMessagePort
sa bawat koneksyon at pinoproseso at sinasagot ang mga mensahe mula sa mga kliyente.
Lifecycle ng isang Shared Worker
Ang Shared Worker
ay nagsisimula kapag ang unang koneksyon ay naitatag at maaaring matapos kapag ang huling port ay isinara. Ang mga koneksyon ay isinasara kapag nireload o isinara ang pahina, at muling nililikha ang worker kung kinakailangan.
Ang Shared Worker
ay isang "matagal na script na ibinabahagi sa loob ng browser". Kung hindi mo binibigyang pansin ang lifecycle ng Shared Worker
, mas malaki ang posibilidad na mangyari ang mga problema tulad ng pagtagas ng mga mapagkukunan, pananatili ng luma o di-nabuburang estado, at mga hindi inaasahang pag-restart.
Subukan: Broadcast sa pagitan ng mga Tab
Ito ay isang simpleng halimbawa kung saan maraming tab ang konektado sa parehong Shared Worker
at anumang mensahe na ipinadala ay ibinabahagi sa lahat ng tab.
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});
- Ang code na ito ay kumokonekta sa isang
Shared Worker
, nag-implement ng pagtanggap/pagpakita ng mensahe, pagpapadala sa pamamagitan ng send form, at pagpapadala ng ping kapag pinindot ang isang 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};
- Nag-implement ang code na ito ng isang simpleng pub/sub feature sa loob ng
Shared Worker
upang ipasa-pasa ang mga mensahe sa maraming kliyente.
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>
- Kung bubuksan mo ang pahinang ito sa maraming tab, ang mensahe mula sa kahit anong tab ay mapapaalam sa iba pang mga tab.
Disenyo ng Mensahe: Request/Response at Correlation ID
Kung maraming kliyente ang gumagamit ng Shared Worker
, kadalasan gusto mong matukoy kung aling response ang para sa aling request. Dahil dito, karaniwan na ang paggamit ng correlation ID upang maiugnay ang mga tugon sa kani-kanilang mga request.
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'
- Nag-implement ang code na ito ng isang simpleng RPC client na kayang magsagawa ng asynchronous na method call sa
Shared Worker
. Dito, ang RPC server (Remote Procedure Call server) ay tumutukoy sa isang server na nagbibigay ng paraan upang tumawag ng mga function at procedure mula sa ibang mga programa o proseso. - Sa halimbawang ito, ang
id
ay basta na lamang dinadagdagan, ngunit maaari kang gumamit ng UUID, random na string, o pagsamahin ang timestamp at counter upang lumikha ng natatanging key.
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};
- Ipinapatupad ng code na ito ang isang simpleng RPC server na nagsasagawa ng proseso ayon sa
method
at ibinabalik ang resulta kasama ng request ID.
Karaniwang Mga Pattern ng Paggamit
May ilang praktikal na pattern kapag gumagamit ng Shared Worker
, gaya ng mga sumusunod:.
Multiplexing ng Isang WebSocket (Ibinabahagi sa Lahat ng Tab)
Kung bawat tab ay magbubukas ng sariling WebSocket connection, maaapektuhan nito ang server load at ang limitasyon ng koneksyon. Gumamit ng iisa lamang na WebSocket sa Shared Worker
, at bawat tab ay magpapadala/tatanggap ng mensahe sa pamamagitan ng 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}
- Ipinapakita ng code na ito ang proseso ng pagpapadala/pagtanggap ng mga mensahe sa WebSocket server at pagtanggap ng koneksyon status notification gamit ang
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};
-
Nag-implement ang code na ito ng mekanismo sa loob ng
Shared Worker
para maibahagi ang iisang WebSocket connection, i-broadcast ang status ng koneksyon at data na natanggap sa lahat ng kliyente, at isinusumite ang mga request ng bawat kliyente papuntang server sa pamamagitan ng WebSocket. -
Sa pamamagitan ng pagsentro ng reconnection strategies at pagpila ng mga hindi pa naipapadalang mensahe sa
Shared Worker
, nagiging pare-pareho ang asal ng lahat ng tab.
Pag-pamagitan sa IndexedDB (Serialization)
Kung sabay-sabay ina-access ang parehong DB mula sa maraming tab at nais mong maiwasan ang conflict dahil sa sabay-sabay na transaction o paghihintay ng lock, maari mong i-queue ang mga ito sa Shared Worker
at iproseso ng sunud-sunod.
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};
- Ang code na ito ay nag-implement ng mekanismo kung saan ang mga read/write operation sa IndexedDB ay ipinila sa pamamagitan ng
Shared Worker
, iniiwasan ang conflict at lock contention sa pamamagitan ng serial na access ng mga tab.
Mga Tip sa Modularization at Bundler
Kung gusto mong isulat ang Shared Worker
script bilang ES Module, ang suporta at behavior ay maaaring magkaiba depende sa environment. Sa aktuwal, mas ligtas na pumili ng isa sa mga sumusunod:.
- Isulat ito sa classic worker format, at gamitin ang
importScripts()
para i-load ang dependencies kung kinakailangan. - Gamitin ang worker entry feature ng iyong bundler (hal. Vite / Webpack / esbuild, atbp.), at gumawa ng hiwalay na bundle para sa
Shared Worker
tuwing build time.
Mga Tip para sa Error Handling, Detection ng Disconnection, at Robustness
Isaalang-alang ang mga sumusunod na punto para sa error handling at robustness:.
-
Paano I-handle ang Pagpapadala Bago maitatag ang Connection I-queue ang mga mensahe na dumating bago maging handa ang port.
-
Pag-detect ng Disconnection Walang standard na
onclose
handler angMessagePort
. Sa pangunahing bahagi, magpadala ng{type: 'bye'}
na mensahe gamit angport.postMessage
sa panahon ngbeforeunload
, o tiyak na ipaliwanag sa protocol upang masiguradong nalilinis ang worker. -
Reconnection Kapag ni-reload ang pahina o binuksan muli ang tab, magkakaroon ng bagong port. Maghanda ng paunang synchronization message (para maipadala agad ang buong estado).
-
Backpressure Kapag malakas ang broadcast, mag-switch sa throttling/debouncing o pagpapadala ng snapshots.
-
Seguridad Ang
Shared Worker
ay batayang ibinabahagi lamang sa loob ng parehong origin. Kung maglalagay ka ng sensitibong impormasyon sa worker, ikonsidera ang pagdisenyo ng side verification gamit ang token o katulad na paraan para sa tumatawag na bahagi.
Paano Gamitin nang Tama ang Dedicated Worker
, Shared Worker
, at Service Worker
Ang bawat uri ng Worker ay may mga sumusunod na katangian:.
-
Dedicated Worker
AngDedicated Worker
ay nilalayong gamitin lamang ng isang pahina. Pinapahintulutan nito ang 1:1 na paghihiwalay ng mga kalkulasyon mula sa UI. -
Shared Worker
AngShared Worker
ay maaaring gamitin sa maraming pahina na may parehong origin. Perpekto ito para sa komunikasyon sa pagitan ng mga tab at pagbabahagi ng isang koneksyon. -
Service Worker
AngService Worker
ay maaaring gamitin para sa network proxying, caching, offline na operasyon, push notifications, at background synchronization. Ang lakas nito ay ang kakayahang harangin ang mga fetch request.
Bilang gabay: gamitin ang
Shared Worker
para sa 'pagbabahagi ng impormasyon at iba't ibang proseso sa pagitan ng mga tab',Service Worker
para sa 'network control', atDedicated Worker
kung nais mo lamang pagaanin ang UI mula sa mabibigat na pagpoproseso.
Karaniwang mga Pagkakamali
Kapag gumagamit ng Shared Worker
, kailangan mong mag-ingat sa mga sumusunod na punto.
-
Nakakalimutang tawagin ang
start()
o hindi kinakailangang mga tawag Kapag gumagamit ngport.addEventListener('message', ...)
, dapat mong tawagin angport.start()
. Hindi ito kailangan kung gumagamit ka ngport.onmessage = ...
. -
Walang Limitasyong Pagbo-broadcast Tumataas ang load habang dumarami ang bilang ng mga tab. Maaari mong bawasan ang load sa pamamagitan ng pagpapatupad ng differential delivery o subscription topics (pag-filter batay sa paksa).
-
Gastos sa Pag-copy ng mga Objekto Dinodoble ng
postMessage
ang data. Para sa malalaking data, isaalang-alang ang pagpapadala nito bilang Transferable (tulad ngArrayBuffer
) o paggamit ng shared memory (SharedArrayBuffer
) kasama angAtomics
. -
Buhay ng Worker at Muling Inisyalisa Ang
Shared Worker
ay maaaring magsara kapag ang huling kliyente ay nag-disconnect. Idisenyo nang maayos ang pag-inisyalisa para sa unang koneksyon at mga pag-restart upang maiwasan ang side effects at mga bug.
Buod
- Ang
Shared Worker
ay mainam para magpatupad ng pampalawig na custom logic na ibinabahagi sa maraming pahina. - Linawin ang kasunduan ng mensahe (mga uri/protocols) at gawing matatag ang disenyo ng request/response sa paglalagay ng correlation IDs.
- Perpekto ito para sa mga proseso na mas mahusay kapag pinag-isa sa lahat ng tab, gaya ng multiplexing ng WebSocket connections o pagseseryal ng access sa IndexedDB.
- Ang maayos na pag-aayos ng mga detalye tulad ng bundlers, type definitions, at reconnection ay maaaring lubos na mapabuti ang maintainability at karanasan ng user.
Maaari mong sundan ang artikulo sa itaas gamit ang Visual Studio Code sa aming YouTube channel. Paki-check din ang aming YouTube channel.