کارگران وب و پردازش هم‌زمان

Web Worker انواع آن، postMessage و onmessage، Structured Clone، Transferable Objects، SharedArrayBuffer و Atomics و پردازش‌های سنگین در مرورگر.

چرا Web Worker؟

جاوا اسکریپت تک‌نخی (Single Threaded) است — محاسبه سنگین UI را قفل می‌کند:

// بدون Worker — صفحه یخ می‌زند: ❌
function heavyTask() {
    let sum = 0;
    for (let i = 0; i < 10 ** 10; i++) sum += i;
}
heavyTask();  // مرورگر ۵ ثانیه بی‌ پاسخ!

// با Worker — UI روان: ✓
const worker = new Worker("worker.js");
worker.postMessage({ task: "heavy" });
// صفحه زنده — کارگر در Thread دیگر کار می‌کند

Web Worker چیست؟

اسکریپتی که در Thread جداگانه اجرا می‌شود:

Dedicated Worker — اختصاصی

// ساخت — worker.js:
self.onmessage = (e) => {
    const { type, data } = e.data;

    switch (type) {
        case "sum":
            const result = data.reduce((a, b) => a + b, 0);
            self.postMessage({ type: "result", value: result });
            break;

        case "primes":
            const primes = findPrimes(data.limit);
            self.postMessage({ type: "primes", value: primes });
            break;
    }
};

function findPrimes(limit) {
    const primes = [];
    for (let n = 2; n <= limit; n++) {
        if (isPrime(n)) primes.push(n);
    }
    return primes;
}

function isPrime(n) {
    for (let i = 2; i <= Math.sqrt(n); i++) {
        if (n % i === 0) return false;
    }
    return n > 1;
}

استفاده از Worker در صفحه اصلی

const worker = new Worker("worker.js");

// دریافت نتیجه:
worker.onmessage = (e) => {
    console.log("از کارگر:", e.data);
};

// خطا:
worker.onerror = (e) => {
    console.log("خطای کارگر:", e.message);
};

// ارسال:
worker.postMessage({ type: "sum", data: [1, 2, 3, 4, 5] });
worker.postMessage({ type: "primes", data: { limit: 1000 } });

// پایان:
worker.terminate();

Worker با Promise — API تمیزتر

class PromiseWorker {
            #worker;
            #pending = new Map();
            #id = 0;

            constructor(url) {
                this.#worker = new Worker(url);
                this.#worker.onmessage = (e) => {
                    const { id, result, error } = e.data;
                    const { resolve, reject } = this.#pending.get(id);
                    this.#pending.delete(id);
                    error ? reject(new Error(error)) : resolve(result);
                };
            }

            run(task, data) {
                return new Promise((resolve, reject) => {
                    const id = ++this.#id;
                    this.#pending.set(id, { resolve, reject });
                    this.#worker.postMessage({ id, task, data });
                });
            }

            terminate() { this.#worker.terminate(); }
        }

// استفاده:
const worker = new PromiseWorker("worker.js");
const sum = await worker.run("sum", [1, 2, 3]);      // 6
const primes = await worker.run("primes", { limit: 100 });
worker.terminate();

Inline Worker — بدون فایل جداگانه

// ساخت از رشته — بدون فایل worker.js:
const code = `
    self.onmessage = (e) => {
        const result = e.data * 2;
        self.postMessage(result);
    };
`;

const blob = new Blob([code], { type: "application/javascript" });
const worker = new Worker(URL.createObjectURL(blob));

worker.onmessage = (e) => console.log(e.data); // 42
worker.postMessage(21);

Structured Clone — کپی داده

postMessage داده را با Structured Clone کپی می‌کند:

// کپی عمیق خودکار — بیشتر انواع پشتیبانی می‌شوند:
worker.postMessage({
    number: 42,
    string: "سلام",
    array: [1, 2, 3],
    object: { nested: { deep: true } },
    date: new Date(),
    map: new Map([["a", 1]]),
    set: new Set([1, 2, 3]),
    typedArray: new Uint8Array([1, 2, 3])
});

// اما این‌ها نمی‌توانند:
// - Function
// - DOM nodes
// - Error (کپی ناقص)
// - Symbol

Transferable Objects — انتقال بدون کپی

// ArrayBuffer می‌تواند منتقل شود — بدون کپی!
const buffer = new ArrayBuffer(100 * 1024 * 1024);  // 100MB

// کپی — کند (کپی کل 100MB): ❌
worker.postMessage({ data: buffer });

// انتقال — فوری (فقط مالکیت): ✓
worker.postMessage({ data: buffer }, [buffer]);

// بعد از انتقال — مالکیت از دست می‌رود:
console.log(buffer.byteLength); // 0 — خالی شد!
بعد از transfer، بافر در Thread مبدأ قابل استفاده نیست — مالکیت کامل منتقل می‌شود.

Shared Worker — کارگر مشترک

// بین چند تب/صفحه مشترک — فقط یک نمونه:
// shared-worker.js:
let connections = 0;

self.onconnect = (e) => {
    const port = e.ports[0];
    connections++;

    port.onmessage = (e) => {
        port.postMessage(`پاسخ برای ${e.data} (اتصال ${connections})`);
    };

    port.postMessage("متصل شدید!");
};

// در هر صفحه:
const worker = new SharedWorker("shared-worker.js");
worker.port.onmessage = (e) => console.log(e.data);
worker.port.start();
worker.port.postMessage("سلام");

SharedArrayBuffer و Atomics

حافظه مشترک بین Threadها — بدون کپی:

// حافظه مشترک:
const sharedBuffer = new SharedArrayBuffer(4);
const sharedArray = new Int32Array(sharedBuffer);

// ارسال به worker — بدون کپی:
worker.postMessage({ sharedBuffer });

// اصلی:
Atomics.store(sharedArray, 0, 100);       // نوشتن امن
console.log(Atomics.load(sharedArray, 0)); // 100

// در Worker — هر دو همان حافظه:
Atomics.add(sharedArray, 0, 50);          // += 50 اتمیک
console.log(Atomics.load(sharedArray, 0)); // 150

// همگام‌سازی — انتظار:
// Worker: Atomics.wait(int32, 0, 0);  — منتظر تغییر از 0
// Main:   Atomics.notify(int32, 0);   — بیدار کردن

پردازش سنگین — مثال عملی

// پردازش تصویر در Worker:
// image-worker.js:
self.onmessage = async (e) => {
    const { imageData, filter } = e.data;
    const { width, height, data } = imageData;

    switch (filter) {
        case "grayscale":
            for (let i = 0; i < data.length; i += 4) {
                const avg = (data[i] + data[i+1] + data[i+2]) / 3;
                data[i] = data[i+1] = data[i+2] = avg;
            }
            break;

        case "invert":
            for (let i = 0; i < data.length; i += 4) {
                data[i] = 255 - data[i];
                data[i+1] = 255 - data[i+1];
                data[i+2] = 255 - data[i+2];
            }
            break;
    }

    self.postMessage({ imageData }, [imageData.data.buffer]);
};

// اصلی:
const canvas = document.querySelector("canvas");
const ctx = canvas.getContext("2d");
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);

const worker = new Worker("image-worker.js");
worker.onmessage = (e) => {
    ctx.putImageData(e.data.imageData, 0, 0);  // نمایش
};
worker.postMessage({ imageData, filter: "grayscale" });

چه چیزی در Worker نیست؟

هست ✓نیست ✗
fetch و XMLHttpRequestDOM (document)
setTimeout / setIntervalwindow object
IndexedDBparent DOM
WebSocketalert/confirm
ImportScriptsdirect UI
atob / btoalocalStorage

جمع‌بندی

کارابزار
محاسبه سنگینDedicated Worker
ارتباط چند تبSharedWorker
کپی دادهStructured Clone (پیش‌فرض)
بافر بزرگ بدون کپیTransferable
حافظه مشترکSharedArrayBuffer + Atomics

این مبحث بخشی از دوره آموزش کامل جاوا اسکریپت است.

ابزارهای مرتبط