Shared Worker di JavaScript
Artikel ini menjelaskan tentang Shared Worker
di JavaScript.
Kami akan menjelaskan semuanya mulai dari dasar-dasar Shared Worker
hingga contoh penggunaannya secara praktis, langkah demi langkah.
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 di JavaScript
Apa itu Shared Worker
?
Shared Worker
adalah thread worker yang dapat digunakan bersama oleh beberapa halaman (tab, jendela, iframe, dan lain-lain) dalam origin yang sama. Tidak seperti Dedicated Worker
yang khusus untuk halaman tertentu, fitur utamanya adalah proses latar belakang tunggal dapat dibagi di beberapa halaman. Contoh penggunaan yang umum antara lain:.
- Anda dapat berbagi satu koneksi WebSocket di beberapa tab, mengurangi jumlah koneksi dan memusatkan proses koneksi ulang.
- Anda dapat menyinkronkan status antar tab (manajemen terpusat untuk Pub/Sub atau store).
- Anda dapat menyusun operasi IndexedDB secara berurutan, untuk memediasi akses secara bersamaan.
- Anda dapat mencegah pelaksanaan proses yang memerlukan komputasi berat secara duplikat.
Shared Worker
memiliki peran yang berbeda dariService Worker
.Service Worker
terutama berfungsi sebagai proxy jaringan, sedangkanShared Worker
berfokus pada melakukan perhitungan arbitrer atau manajemen status yang dibagikan antar halaman.
API Dasar dan Siklus Hidup
Membuat (Thread Utama)
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 });
- Kode ini menunjukkan cara membuat
Shared Worker
dan mengirim/menerima pesan melalui port-nya.
Penerima (dalam lingkup 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)
- Kode ini menunjukkan bagaimana, di dalam
Shared Worker
, sebuahMessagePort
diterima untuk setiap koneksi dan memproses serta membalas pesan dari klien.
Siklus Hidup dari Shared Worker
Shared Worker
dimulai ketika koneksi pertama terjalin dan dapat berhenti ketika port terakhir ditutup. Koneksi ditutup saat halaman dimuat ulang/ditutup, dan worker akan dibuat ulang jika diperlukan.
Shared Worker
adalah "skrip berjangka panjang yang dibagikan di dalam peramban". Jika Anda tidak memperhatikan siklus hidup Shared Worker
, masalah seperti kebocoran sumber daya, persistensi status yang usang, dan restart yang tidak diinginkan lebih mungkin terjadi.
Coba Sendiri: Broadcast Antar Tab
Ini adalah implementasi minimal di mana beberapa tab terhubung ke Shared Worker
yang sama dan setiap pesan yang dikirim akan disiarkan ke semua 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});
- Kode ini menghubungkan ke
Shared Worker
, mengimplementasikan penerimaan/tampilan pesan, mengirim pesan dari formulir kirim, dan mengirim ping dengan menekan sebuah tombol.
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};
- Kode ini mengimplementasikan fitur pub/sub sederhana di dalam
Shared Worker
untuk mengirimkan pesan antar berbagai klien.
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>
- Jika Anda membuka halaman ini di beberapa tab, pesan yang dikirim dari tab mana pun akan diberitahukan ke tab lainnya.
Desain Pesan: Permintaan/Tanggapan dan Correlation ID
Ketika beberapa klien berinteraksi dengan Shared Worker
, seringkali Anda ingin mengidentifikasi respon mana yang sesuai dengan permintaan tertentu. Oleh karena itu, praktik standar adalah menyertakan ID korelasi untuk menghubungkan respons dengan permintaan.
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'
- Kode ini mengimplementasikan klien RPC sederhana yang dapat memanggil metode secara asinkron ke
Shared Worker
. Di sini, server RPC (Remote Procedure Call) mengacu pada server yang menyediakan mekanisme untuk memanggil fungsi dan prosedur dari program atau proses lain. - Pada contoh ini,
id
hanya ditingkatkan secara sederhana, tetapi Anda juga dapat menggunakan UUID, string acak, atau menggabungkan timestamp dengan penghitung untuk membuat kunci unik.
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};
- Kode ini mengimplementasikan server RPC sederhana yang melakukan pemrosesan sesuai dengan
method
dan mengirimkan hasilnya bersama dengan ID permintaan.
Pola Penggunaan Umum
Ada beberapa pola praktis saat menggunakan Shared Worker
, seperti berikut:.
Multiplexing WebSocket Tunggal (Bersama Antar Tab)
Jika setiap tab membuka koneksi WebSocket sendiri, hal ini akan mempengaruhi beban server dan batas koneksi. Tempatkan hanya satu WebSocket di dalam Shared Worker
, dan setiap tab mengirim/menerima pesan melalui worker tersebut.
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}
- Kode ini mengimplementasikan proses pengiriman/penerimaan pesan ke server WebSocket dan menerima notifikasi status koneksi melalui
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};
-
Kode ini mengimplementasikan mekanisme di dalam
Shared Worker
untuk membagi satu koneksi WebSocket, menyiarkan status koneksi dan data yang diterima ke semua klien, serta mengirim permintaan keluar dari masing-masing klien ke server melalui WebSocket. -
Dengan memusatkan strategi koneksi ulang dan antrean pesan yang belum terkirim di
Shared Worker
, perilaku menjadi konsisten di semua tab.
Mediasi IndexedDB (Serialisasi)
Saat mengakses database yang sama dari beberapa tab, jika Anda ingin menghindari konflik dari transaksi bersamaan atau menunggu kunci, Anda dapat mengantrikannya di dalam Shared Worker
dan memprosesnya secara serial.
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};
- Kode ini mengimplementasikan mekanisme di mana operasi baca/tulis ke IndexedDB diantrikan melalui
Shared Worker
, men-serialisasi akses dari beberapa tab untuk menghindari konflik dan persaingan kunci.
Tips Modularisasi dan Bundler
Jika Anda ingin menulis skrip Shared Worker
Anda sebagai ES Module, perilaku dan dukungan dapat bervariasi tergantung lingkungan. Dalam praktik, lebih aman untuk memilih salah satu dari berikut:.
- Tulis dalam format worker klasik, dan gunakan
importScripts()
untuk memuat dependensi jika diperlukan. - Gunakan fitur entry worker dari bundler Anda (seperti Vite / Webpack / esbuild, dll.), dan buat bundle terpisah untuk
Shared Worker
saat proses build.
Tips Penanganan Error, Deteksi Putus Koneksi, dan Ketahanan
Pertimbangkan poin-poin berikut terkait penanganan error dan ketahanan:.
-
Menangani Pengiriman Sebelum Koneksi Terjadi Antrikan pesan yang datang sebelum port siap.
-
Deteksi Putus Koneksi
MessagePort
tidak memiliki handler standaronclose
. Di sisi utama, kirim pesan{type: 'bye'}
menggunakanport.postMessage
saatbeforeunload
, atau jelaskan secara jelas dalam protokol untuk memastikan worker dibersihkan. -
Koneksi Ulang Saat halaman dimuat ulang atau tab dibuka kembali, port baru akan dibuat. Siapkan pesan sinkronisasi awal (untuk mengirimkan status lengkap secara sekaligus).
-
Tekanan Balik (Backpressure) Pada saat penyiaran berat, lakukan throttling/debouncing atau mengirimkan snapshot.
-
Keamanan
Shared Worker
secara fundamental dibagikan dalam origin yang sama. Jika Anda menempatkan data rahasia di dalam worker, pertimbangkan merancang verifikasi sisi pemanggil dengan token atau metode serupa.
Cara Menggunakan Dedicated Worker
, Shared Worker
, dan Service Worker
dengan Tepat
Setiap jenis Worker memiliki karakteristik sebagai berikut:.
-
Dedicated Worker
Dedicated Worker
dimaksudkan hanya untuk digunakan oleh satu halaman saja. Ini memungkinkan pemisahan komputasi secara 1:1 dari antarmuka pengguna (UI). -
Shared Worker
Shared Worker
dapat digunakan bersama oleh beberapa halaman dengan asal (origin) yang sama. Ini sangat ideal untuk komunikasi antar tab dan berbagi satu koneksi. -
Service Worker
Service Worker
dapat digunakan untuk proxy jaringan, caching, operasi offline, notifikasi push, dan sinkronisasi di latar belakang. Kekuatan utamanya adalah kemampuannya untuk mencegat permintaan fetch.
Sebagai aturan umum: gunakan
Shared Worker
untuk 'berbagi informasi dan pemrosesan arbitrer antar tab',Service Worker
untuk 'kontrol jaringan', danDedicated Worker
jika Anda hanya ingin memindahkan pemrosesan berat dari UI.
Kesalahan Umum
Saat menggunakan Shared Worker
, Anda perlu memperhatikan hal-hal berikut.
-
Lupa memanggil
start()
atau pemanggilan yang tidak diperlukan Saat menggunakanport.addEventListener('message', ...)
, Anda harus memanggilport.start()
. Ini tidak diperlukan jika Anda menggunakanport.onmessage = ...
. -
Penyiaran Tanpa Batas Beban akan meningkat seiring bertambahnya jumlah tab. Anda dapat mengurangi beban dengan menerapkan differential delivery atau topik langganan (penyaringan berdasarkan topik).
-
Biaya Penyalinan Objek
postMessage
menduplikasi data. Untuk data besar, pertimbangkan untuk mengirimnya sebagai Transferable (sepertiArrayBuffer
) atau menggunakan memori bersama (SharedArrayBuffer
) bersamaAtomics
. -
Masa Aktif dan Inisialisasi Ulang
Shared Worker
dapat berhenti ketika klien terakhir terputus. Rancang inisialisasi dengan benar untuk koneksi pertama dan saat restart guna menghindari efek samping dan bug.
Ringkasan
Shared Worker
adalah tempat untuk mengimplementasikan logika kustom jangka panjang yang dibagikan di beberapa halaman.- Perjelas kontrak pesan (tipe/protokol) dan buat desain permintaan/respons Anda tangguh dengan melampirkan correlation ID.
- Ini ideal untuk proses yang bekerja lebih baik ketika disatukan di semua tab, seperti multiplexing koneksi WebSocket atau serialisasi akses ke IndexedDB.
- Penyempurnaan detail seperti bundler, definisi tipe, dan mekanisme reconnect dapat sangat meningkatkan kemudahan pemeliharaan dan pengalaman pengguna.
Anda dapat mengikuti artikel di atas menggunakan Visual Studio Code di saluran YouTube kami. Silakan periksa juga saluran YouTube kami.