JavaScript'te Paylaşılan İşçi (Shared Worker)

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 &amp; 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 Workerdan 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 bir MessagePort 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, methoda 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 Workerda 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 bir onclose olay yöneticisi yoktur. Ana tarafta, beforeunload sırasında port.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çin Service Worker ve yalnızca ağır işlemleri arayüzden ayırmak istiyorsanız Dedicated 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 yapmak port.addEventListener('message', ...) kullandığınızda, mutlaka port.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 birlikte Atomics 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.

YouTube Video