1234 words
6 minutes
Web Workers in JS

Web Workers في JavaScript#

مقدمة 💡#

النهاردة هنتكلم عن الـ Web Workers! تخيل معايا إنك عندك مساعد سوبر مان بيشتغل في الباك جراوند ويخلص الشغل التقيل، وانت قاعد مرتاح!

ليه محتاجين Web Workers أصلاً؟ 🤔#

JavaScript بتشتغل على thread واحد (single-threaded)، يعني بتعمل حاجة واحدة في وقت واحد. تخيل إنك في مطعم فيه ويتر واحد بس - هيتلخبط صح؟ Web Workers زي ما تكون جبت ويترز تانيين يساعدوا!

المشاكل اللي Web Workers بتحلها:#

  • 🐌 التطبيق مش هيهنج
  • 💪 تقدر تعمل عمليات معقدة في الباكجراوند
  • 🎯 الصفحة هتفضل responsive على طول مش الـCSS لا يعني reactive معاك
  • ⚡ أداء أحسن بكتير

يلا نشوف الكود! 👨‍💻#

1️⃣ إزاي نعمل Worker جديد؟#

// worker.js
self.onmessage = function (e) {
    const number = e.data;
    const result = fibonacci(number);
    postMessage(result);
};

function fibonacci(n) {
    if (n <= 1) return n;
    return fibonacci(n - 1) + fibonacci(n - 2);
}

في الكود ده، بنجهز الـ worker علشان يستقبل الرسائل باستخدام self.onmessage، ويعمل المهام التقيلة (زي حساب رقم Fibonacci)، وبعدين يبعت النتيجة باستخدام postMessage
ممكن تقولي إستني ي عم هو ف إيه و إيه هو self؟ أو إيه هي postMessage و onmessage؟

  • Web Workers بيشتغلوا في global context مختلف، مش الـ window اللي احنا عارفينه بنسمي الglobal context الخاص ده self!
  • Web Workers بيتواصلوا مع الـ main thread باستخدام الأحداث (events)
  • باستخدام الـevents ، يقدروا يبعتوا ويستقبلوا رسائل أو بيانات في الكود،
  • onmessage عادة بيستقبل رسائل أو بيانات من الـ main thread
  • postMessage بيبعت البيانات المعالجة من الـ Web Worker لل main thread

2️⃣ الخطوة التانية إنشاء والتحدث مع الـ Worker#

// main.js

// إنشاء Web Worker جديد باستخدام Worker
const worker = new Worker("worker.js");

// إرسال بيانات للـ worker
worker.postMessage(40); // حساب الرقم 40 فى Fibonacci

// استلام البيانات من الـ worker
worker.onmessage = function (e) {
    console.log("The result is:", e.data);
     // إنهاء الـ worker بعد الحصول على النتيجة
    worker.terminate();
};

// التعامل مع أخطاء الـ worker
worker.onerror = function (error) {
    console.error("Worker error:", error);
    
    // إنهاء الـ worker في حالة الخطأ
    worker.terminate();
};

هنا، بننشئ worker جديد، نبعت رسالة ليه باستخدام worker.postMessage ونستلم النتائج باستخدام worker.onmessage كمان بنتعامل مع أي أخطاء محتملة باستخدام worker.onerror

طب م دا برضه كلام حلو بس مش كفاية أنا عاوز أشوف سيناريوهات تفيدني واقعية

أمثلة عملية 🎯#

1. ترتيب Array كبيرة جداً#

لو عندك array كبيرة جدا عاوز تعملها sort خلينا نخلي Web Worker يتعامل مع الموضوع ده

// worker.js
self.onmessage = (event) => {
    const numbers = event.data;
    const sortedArray = numbers.sort((a, b) => a - b);
    self.postMessage(sortedArray);
};

// main.js
const bigArray = Array.from({ length: 1000000 }, () => Math.random());
const sortWorker = new Worker('worker.js');

sortWorker.postMessage(bigArray);
sortWorker.onmessage = (event) => {
    console.log('Array اترتبت خلاص:', event.data);
};

2. التعامل مع API Calls#

عندك api بتعملها fecth وبتغيب ؟ هييجي واحد يقولي أنا شغال Asynchronous هقوله ماشي ي حبيبي ده مبيغيرش حقيقة إن fetch بيشتغل على الـ main thread حتى لو استخدمت async/await

// worker.js
self.onmessage = async (event) => {
    const { url } = event.data;
    try {
        const response = await fetch(url);
        const data = await response.json();
        self.postMessage({ status: 'success', data });
    } catch (error) {
        self.postMessage({ status: 'error', error: error.message });
    }
};

// main.js
const apiWorker = new Worker('api-worker.js');
apiWorker.postMessage({ 
    url: 'https://api.example.com/data' 
});

نصائح مهمة جداً 🌟#

1. إمتى تستخدم Web Workers؟#

  • ✅ مع العمليات الحسابية المعقدة
  • ✅ تحليل ملفات كبيرة
  • ✅ معالجة الصور
  • ✅ API calls اللي بتاخد وقت
  • ❌ عمليات DOM (مش هتنفع خالص)

2. Best Practices 📝#

  • اعمل error handling كويس
  • متنساش تعمل terminate للـ worker لما تخلص
  • خلي الـ messages صغيرة على قد ما تقدر
  • استخدم transferable objects للـ performance

إيه هي Transferable Objects؟ 🤔#

تخيل معايا الموقف ده:

  • عندك array كبير جداً (مثلاً 100MB)
  • عايز تبعته للـ worker
  • بالطريقة العادية، JavaScript هتعمل copy للداتا
  • هياخد وقت ويستهلك ذاكرة مضاعفة

مع Transferable Objects:

  • مفيش copying وهي دي الـpoint كلها
  • أسرع 1000 مرة
  • مفيش استهلاك ذاكرة إضافي

أمثلة عملية علي الـTransferable Objects👨‍💻#

1. مثال بسيط باستخدام ArrayBuffer#

// main.js
// نعمل array buffer كبير
const buffer = new ArrayBuffer(100 * 1024 * 1024); // 100MB
const view = new Uint8Array(buffer);

// نملا الـ buffer بداتا
for (let i = 0; i < view.length; i++) {
    view[i] = i % 256;
}

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

console.log('قبل التحويل:', buffer.byteLength); // هيطبع 100MB

// نبعت الـ buffer للـ worker مع transfer
worker.postMessage(buffer, [buffer]);

console.log('بعد التحويل:', buffer.byteLength); // هيطبع 0 لأن الملكية اتنقلت
// worker.js
self.onmessage = (event) => {
    const buffer = event.data;
    console.log('حجم الـ buffer في الـ worker:', buffer.byteLength); // هيطبع 100MB
    
    // نعمل شغلنا على الـ buffer
    const view = new Uint8Array(buffer);
    // ... نعمل processing
    
    // نرجع الـ buffer للـ main thread
    self.postMessage(buffer, [buffer]);
};

2. مثال عملي: معالجة صورة 🖼#

// main.js
// نفترض إن عندنا canvas فيه صورة
const canvas = document.querySelector('canvas');
const ctx = canvas.getContext('2d');

// نحول الصورة لـ ImageData
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);

const worker = new Worker('image-worker.js');

// نبعت الـ buffer بتاع الصورة
worker.postMessage(imageData.data.buffer, [imageData.data.buffer]);

worker.onmessage = (event) => {
    // نستقبل الصورة المعدلة
    const newBuffer = event.data;
    const newImageData = new ImageData(
        new Uint8ClampedArray(newBuffer),
        canvas.width,
        canvas.height
    );
    ctx.putImageData(newImageData, 0, 0);
};
// image-worker.js
self.onmessage = (event) => {
    const buffer = event.data;
    const pixels = new Uint8ClampedArray(buffer);
    
    // نعمل فلتر على الصورة (مثلاً نخليها grayscale)
    for (let i = 0; i < pixels.length; i += 4) {
        const avg = (pixels[i] + pixels[i + 1] + pixels[i + 2]) / 3;
        pixels[i] = avg;     // Red
        pixels[i + 1] = avg; // Green
        pixels[i + 2] = avg; // Blue
        // Alpha channel نسيبه زي ما هو
    }
    
    // نرجع الـ buffer المعدل
    self.postMessage(buffer, [buffer]);
};

أنواع Transferable Objects 📦#

  1. ArrayBuffer
  2. MessagePort
  3. ImageBitmap
  4. OffscreenCanvas
  5. AudioData
  6. VideoFrame

نصائح مهمة للـTransferable Objects⚡#

  1. لما تعمل transfer للـ object:

    • ✅ الـ object الأصلي بيتمسح من الـ source
    • ❌ متقدرش تستخدمه تاني في نفس الـ thread
  2. استخدم Transferable Objects مع:

    • ✅ البيانات الكبيرة
    • ✅ العمليات اللي محتاجة performance عالي
    • ✅ معالجة الصور والفيديو
  3. حاجات تاخد بالك منها:

    • اعمل check إن الـ object فعلاً transferable
    • احتفظ بـ copy لو هتحتاجها
    • متنساش تتعامل مع الـ errors

مثال للـ Performance Comparison 📊#

// قياس الوقت بدون transfer
const normalBuffer = new ArrayBuffer(100 * 1024 * 1024);
console.time('بدون transfer');
worker.postMessage(normalBuffer);
console.timeEnd('بدون transfer');
// النتيجة: ~100ms

// قياس الوقت مع transfer
const transferBuffer = new ArrayBuffer(100 * 1024 * 1024);
console.time('مع transfer');
worker.postMessage(transferBuffer, [transferBuffer]);
console.timeEnd('مع transfer');
// النتيجة: ~1ms

حاجات مهم تعرفها 🎓#

  1. Web Workers معندهمش access للـ:
    • DOM
    • window object
    • document object
  2. Web Workers عندهم access للـ:
    • XMLHttpRequest
    • WebSockets
    • IndexedDB
    • Custom APIs

إزاي Web Workers بتعمل Multi-threading? 🤯#

الموضوع بسيط:

  1. JavaScript نفسها single-threaded
  2. Web Workers مش جزء من JavaScript - دي feature في المتصفح
  3. المتصفح بيعمل thread جديد لكل worker
  4. الـ communication بيتم عن طريق messages (أمان على الآخر)

زي ما دايمًا بنسمع، JavaScript بيتوصف على إنه single-threaded language، وده معناه إنه شغال على main thread واحدة، اللي المتصفح بيعمل فيها معظم الشغل اللي بيظهر للمستخدم في الواجهة. يعني الحاجات اللي بتحصل جوا الـ main thread بتشمل scripting (تشغيل أكواد JavaScript)، بعض عمليات rendering (زي إعادة رسم العناصر)، تحليل ملفات HTML وCSS، وحاجات تانية مرتبطة مباشرة بتجربة المستخدم.

لكن، الحقيقة إن المتصفحات مش بتشتغل على thread واحدة بس، دي بتستخدم threads تانية لحاجات معينة ورا الكواليس زي الـ GPU threads اللي بتتعامل مع الرسوميات، ودي حاجات المطور عادةً مش بيكون ليه تحكم مباشر فيها.

بالنسبة للـ JavaScript، الشغل في العادة بيكون محصور في الـ main thread، لكن ده “افتراضيًا” بس. ممكن فعلًا نستخدم multi-threading في JavaScript باستخدام خاصية اسمها Web Workers API.

الـ Web Workers مفيدين لما يكون عندك عمليات حسابية تقيلة مش ممكن تتعمل على الـ main thread من غير ما تسبب “long tasks” بتخلي الصفحة مش مستجيبة. العمليات دي ممكن تأثر على مقياس Interaction to Next Paint (INP)، فلو عندك شغل ممكن يتعمل خارج الـ main thread، ده ممكن يساعد على توفير مساحة للمهام التانية على الـ main thread عشان استجابة التفاعلات تكون أسرع.

الخلاصة 🎉#

Web Workers حل جامد جداً للـ performance، بس لازم تستخدمهم صح:

  • ✅ فكر كويس قبل ما تستخدمهم
  • ✅ اختار المهام المناسبة
  • ✅ اعمل error handling كويس
  • ✅ متنساش الـ cleanup

Join our whatsapp group here
My Channel here