JavaScript'te Paylaşılan İşçi (Shared Worker)
Bu makale, JavaScript'teki Shared Worker
'ı açıklar.
Shared Worker
temellerinden, pratik kullanım örneklerine kadar her şeyi adım adım açıklayacağız.
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>
JavaScript'te Paylaşılan İşçi (Shared Worker)
Shared Worker
nedir?
Shared Worker
, aynı kökende bulunan birden fazla sayfa (sekme, pencere, iframe vb.) arasında paylaşılabilen bir işçi (worker) iş parçacığıdır. Sayfaya özel bir Dedicated Worker
dan farklı olarak, ana özellik tek bir arka plan işleminin birden fazla sayfa arasında paylaşılabilmesidir. Tipik kullanım durumları şunları içerir:.
- Tek bir WebSocket bağlantısını birden fazla sekme arasında paylaşabilirsiniz, bağlantı sayısını azaltır ve yeniden bağlantıları merkezileştirir.
- Sekmeler arasındaki durumu senkronize edebilirsiniz (Pub/Sub veya depolar için merkezi yönetim).
- IndexedDB işlemlerini sıralayabilir (ölçülü hale getirebilir), eşzamanlı erişimi yönetebilirsiniz.
- Hesaplama açısından pahalı işlemlerin tekrarlı olarak çalışmasını önleyebilirsiniz.
Shared Worker
,Service Worker
'dan farklı bir rol üstlenir.Service Worker
esas olarak bir ağ vekili olarak görev yaparken,Shared Worker
ise sayfalar arası paylaşılan keyfi hesaplamalar veya durum yönetimi üzerinde odaklanır.
Temel API ve Yaşam Döngüsü
Oluşturma (Ana İş Parçacığı)
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 });
- Bu kod, bir
Shared Worker
'ın nasıl oluşturulacağını ve portu aracılığıyla mesaj gönderilip alınabileceğini gösterir.
Shared Worker
kapsamı içinde alıcı
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)
- Bu kod,
Shared Worker
içinde her bağlantı için birMessagePort
alındığını ve istemcilerden gelen mesajların işlenip cevaplandığını gösterir.
Shared Worker
'ın Yaşam Döngüsü
Shared Worker
, ilk bağlantı kurulduğunda başlar ve son bağlantı noktası kapatıldığında sona erebilir. Sayfa yeniden yüklendiğinde veya kapatıldığında bağlantılar kapanır ve gerekirse worker yeniden oluşturulur.
Shared Worker
, "tarayıcı içinde paylaşılan uzun ömürlü bir betiktir". Shared Worker
yaşam döngüsüne dikkat etmezseniz, kaynak sızıntıları, eski durumun devam etmesi ve istenmeyen yeniden başlatmalar gibi sorunlar ortaya çıkabilir`.
Deneyin: Sekmeler Arasında Yayın (Broadcast)
Bu, birden fazla sekmenin aynı Shared Worker
'a bağlandığı ve gönderilen her mesajın tüm sekmelere yayınlandığı minimal bir uygulamadır.
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});
- Bu kod, bir
Shared Worker
'a bağlanır; mesaj alımı/gösterimi, bir gönderim formundan mesaj gönderimi ve bir düğmeye tıklayarak ping gönderimini uygular.
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};
- Bu kod, birden çok istemci arasında mesaj iletimi için
Shared Worker
içinde basit bir pub/sub özelliği uygular.
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>
- Bu sayfayı birden fazla sekmede açarsanız, herhangi bir sekmeden gönderilen mesajlar diğerlerine bildirilir.
Mesaj Tasarımı: İstek/Cevap ve Korelasyon ID'si
Birden fazla istemci bir Shared Worker
ile etkileşime girdiğinde, genellikle hangi yanıtın hangi isteğe ait olduğunu belirlemek istersiniz. Bu nedenle, yanıtları isteklerle ilişkilendirmek için korelasyon kimliği (correlation ID) eklemek standart bir uygulamadır.
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'
- Bu kod,
Shared Worker
'a asenkron yöntem çağrıları yapabilen basit bir RPC istemcisi uygular. Burada, bir RPC sunucusu (Uzaktan Prosedür Çağrısı sunucusu), diğer programlardan veya süreçlerden işlevlerin ve prosedürlerin çağrılmasına olanak sağlayan bir sunucuyu ifade eder. - Bu örnekte,
id
basitçe artırılır, ancak benzersiz bir anahtar oluşturmak için bir UUID, rastgele bir dize veya bir zaman damgası ile bir sayıcıyı da birleştirebilirsiniz.
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};
- Bu kod,
method
a göre işlemi gerçekleştiren ve sonucu istek kimliğiyle birlikte geri gönderen basit bir RPC sunucusunu uygular.
Tipik Kullanım Şablonları
Shared Worker
kullanırken aşağıdaki gibi çeşitli pratik kalıplar vardır:.
Tek Bir WebSocket'i Birden Fazla Sekmede Paylaşmak (Multiplexing)
Her sekme kendi WebSocket bağlantısını açarsa, bu sunucunun yükünü ve bağlantı limitini etkiler. Shared Worker
içine yalnızca bir WebSocket yerleştirip, her sekmenin mesaj gönderip almasını worker üzerinden sağlayabilirsiniz.
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}
- Bu kod, bir WebSocket sunucusuna mesaj gönderme/alma ve bağlantı durumu bildirimlerini bir
Shared Worker
üzerinden alma sürecini uygular.
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};
-
Shared Worker
içinde tek bir WebSocket bağlantısını paylaşma, bağlantı durumunu ve alınan verileri tüm istemcilere yayınlama ve her istemcinin dışa giden isteklerini WebSocket üzerinden sunucuya iletme mekanizmasını uygular. -
Yeniden bağlantı stratejilerini ve gönderilmeyen mesajların kuyruğa alınmasını
Shared Worker
da merkezi hale getirerek, tüm sekmelerde davranış tutarlı olur.
IndexedDB Arabuluculuğu (Serileştirme)
Aynı veritabanına birden çok sekmeden erişirken, eşzamanlı işlemlerden kaynaklanan çakışmalardan veya kilit beklemeden kaçınmak isterseniz, işlemleri Shared Worker
'da sıraya koyup, seri şekilde işleyebilirsiniz.
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};
- Bu kod, IndexedDB'ye okuma/yazma işlemlerinin
Shared Worker
aracılığıyla sıraya alındığı, böylece birden fazla sekmeden gelen erişimi serileştirip çakışma ve kilitlenmeleri önleyen bir mekanizma uygular.
Modülerleştirme ve Paketleme İpuçları
Shared Worker
scriptinizi bir ES Modülü olarak yazmak isterseniz, davranış ve destek ortama göre değişebilir. Pratikte, aşağıdakilerden birini seçmek daha güvenlidir:.
- Klasik worker formatında yazın ve gerekiyorsa bağımlılıkları yüklemek için
importScripts()
kullanın. - Paketleyicinizin (Vite / Webpack / esbuild vb.) worker giriş noktasını kullanarak, derleme sırasında
Shared Worker
için ayrı bir paket oluşturun.
Hata Yönetimi, Bağlantı Kopma Tespiti ve Sağlamlık için İpuçları
Hata yönetimi ve sağlamlık için şu noktaları göz önünde bulundurun:.
-
Bağlantı Kurulmadan Önceki Gönderimler Port hazır olmadan gelen mesajları sıraya alın.
-
Bağlantı Kopmasını Tespit Etme
MessagePort
'un standart bironclose
olay yöneticisi yoktur. Ana tarafta,beforeunload
sırasındaport.postMessage
ile{type: 'bye'}
mesajı gönderin veya aksi takdirde protokolde açıkça belirtin ki worker'ın temizlenmesi sağlansın. -
Yeniden Bağlanma (Reconnection) Bir sayfa yenilendiğinde veya sekme yeniden açıldığında yeni bir port oluşturulur. İlk eşitleme mesajı hazırlayın (tüm durumu tek seferde göndermek için).
-
Geri Basınç (Backpressure) Yönetimi Yoğun yayın sırasında kısıtlama (throttling)/gecikme (debouncing) veya anlık görüntü göndermeye geçin.
-
Güvenlik
Shared Worker
temelde aynı kökende paylaşılır. Worker içerisine gizli veriler koyuyorsanız, çağıran taraf için token veya benzeri yöntemlerle kimlik doğrulama tasarlamayı değerlendirin.
Dedicated Worker
, Shared Worker
ve Service Worker
'ı Uygun Şekilde Nasıl Kullanmalı
Her Worker türünün aşağıdaki özellikleri vardır:.
-
Dedicated Worker
Dedicated Worker
yalnızca tek bir sayfa tarafından kullanılmak üzere tasarlanmıştır. Hesaplamaları arayüzden 1:1 oranında ayırmayı sağlar. -
Shared Worker
Shared Worker
, aynı origin'e sahip birden fazla sayfa arasında paylaşılabilir. Sekmeler arası iletişim ve tek bir bağlantının paylaşımı için idealdir. -
Service Worker
Service Worker
, ağ proxy'si, önbellekleme, çevrimdışı işlemler, push bildirimleri ve arka plan senkronizasyonu için kullanılabilir. En güçlü yanı fetch isteklerini yakalayabilme yeteneğidir.
Genel kural olarak: Sekmeler arası 'bilgi paylaşımı ve keyfi işlemler' için
Shared Worker
, 'ağ kontrolü' içinService Worker
ve yalnızca ağır işlemleri arayüzden ayırmak istiyorsanızDedicated Worker
kullanın.
Yaygın Hatalar
Shared Worker
kullanırken aşağıdaki noktalara dikkat etmelisiniz.
-
start()
fonksiyonunu çağırmayı unutmak veya gereksiz çağrılar yapmakport.addEventListener('message', ...)
kullandığınızda, mutlakaport.start()
çağırmalısınız.port.onmessage = ...
kullanırsanız bu gereksizdir. -
Kontrolsüz Yayın Sekme sayısı arttıkça yük de artar. Yükü farklı teslimat veya abonelik konuları (konuya göre filtreleme) uygulayarak azaltabilirsiniz.
-
Nesnelerin Kopyalama Maliyeti
postMessage
verinin kopyasını oluşturur. Büyük veriler için, Transferable (örn.ArrayBuffer
) olarak göndermeyi veya paylaşılan bellek (SharedArrayBuffer
) ile birlikteAtomics
kullanmayı düşünün. -
Yaşam Süresi ve Yeniden Başlatma Son istemci ayrıldığında
Shared Worker
sonlandırılabilir. Yan etkiler ve hatalardan kaçınmak için ilk bağlantılar ve yeniden başlatmalar için başlatmayı düzgün tasarlayın.
Özet
Shared Worker
, birden fazla sayfada paylaşılan uzun ömürlü özel mantığın uygulanacağı yerdir.- Mesaj sözleşmesini (tipler/protokoller) netleştirin ve ilişkilendirme kimlikleri ekleyerek istek/yanıt tasarımınızı sağlamlaştırın.
- WebSocket bağlantılarını çoğullamak veya IndexedDB'ye erişimi sıralamak gibi tüm sekmelerde tek tip çalışan işlemler için idealdir.
- Paketleyiciler, tip tanımları ve yeniden bağlantı gibi detayların incelenmesi, bakım kolaylığını ve kullanıcı deneyimini büyük ölçüde artırabilir.
Yukarıdaki makaleyi, YouTube kanalımızda Visual Studio Code'u kullanarak takip edebilirsiniz. Lütfen YouTube kanalını da kontrol edin.