Shared Worker sa JavaScript

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 &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>

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 sa Service Worker. Ang pangunahing papel ng Service Worker ay magsilbing network proxy, habang ang Shared 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 ng MessagePort 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 ang MessagePort. Sa pangunahing bahagi, magpadala ng {type: 'bye'} na mensahe gamit ang port.postMessage sa panahon ng beforeunload, 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 Ang Dedicated Worker ay nilalayong gamitin lamang ng isang pahina. Pinapahintulutan nito ang 1:1 na paghihiwalay ng mga kalkulasyon mula sa UI.

  • Shared Worker Ang Shared 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 Ang Service 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', at Dedicated 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 ng port.addEventListener('message', ...), dapat mong tawagin ang port.start(). Hindi ito kailangan kung gumagamit ka ng port.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 ng ArrayBuffer) o paggamit ng shared memory (SharedArrayBuffer) kasama ang Atomics.

  • 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.

YouTube Video