کارگران وب و پردازش همزمان
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 جداگانه اجرا میشود:
- دسترسی به DOM و window ندارد
- با
postMessageبا صفحه اصلی حرف میزند - برای محاسبه، پارس فایل، پردازش تصویر
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 و XMLHttpRequest | DOM (document) |
| setTimeout / setInterval | window object |
| IndexedDB | parent DOM |
| WebSocket | alert/confirm |
| ImportScripts | direct UI |
| atob / btoa | localStorage |
جمعبندی
| کار | ابزار |
|---|---|
| محاسبه سنگین | Dedicated Worker |
| ارتباط چند تب | SharedWorker |
| کپی داده | Structured Clone (پیشفرض) |
| بافر بزرگ بدون کپی | Transferable |
| حافظه مشترک | SharedArrayBuffer + Atomics |
این مبحث بخشی از دوره آموزش کامل جاوا اسکریپت است.