Web Workers بالعربي: خلّي الحسابات الثقيلة بعيد عن الواجهة
مستوى القارئ: متوسط
هتخرج من المقال بطريقة عملية تمنع حساب JavaScript تقيل من تجميد الواجهة، بدل ما المستخدم يضغط زرار ويستنى الصفحة تفوق.
المشكلة باختصار
اللي بيحصل فعلاً إن JavaScript في المتصفح بيشتغل غالبًا على الـ main thread. نفس الخيط ده مسؤول عن الكليك، الرسم، تحديث الـ DOM، وتنفيذ الكود. لو حطيت عليه حساب بياخد 800ms، الواجهة هتبقى متجمدة طول المدة دي.
سيناريو واقعي: عندك لوحة أسعار بتستورد 50 ألف صف من CSV، وبعدها تحسب margin وtax وdiscount لكل صف. على لابتوب قوي ممكن الحساب ياخد 250ms. على موبايل متوسط ممكن يوصل 900ms. الرقم ده كفاية يخلي المستخدم يحس إن الصفحة علقت.
الفكرة بمثال بسيط
ركز في المثال ده: عندك موظف استقبال واحد. لو خليته يرد على العملاء ويحسب كشف حساب 50 ألف صف في نفس الوقت، العملاء هيقفوا. الأفضل إنك تبعت كشف الحساب لمحاسب في غرفة تانية، ولما يخلص يرجّع النتيجة. الاستقبال يفضل يرد على الناس.
ده بالظبط دور Web Worker. الواجهة تبعت بيانات للـ worker باستخدام postMessage. العامل يحسب بعيد عن الـ main thread. وبعدها يرجع النتيجة برسالة تانية. المكسب إن الكليك والـ typing يفضلوا شغالين. الخسارة إن فيه تكلفة نقل بيانات بين الخيطين.
مثال تنفيذي قابل للنسخ
الافتراض إن عندك حساب CPU-bound واضح: فلترة أو تجميع أو parsing كبير. المثال التالي يحسب إجمالي أسعار عدد كبير من المنتجات خارج الواجهة.
// main.js
const worker = new Worker("/pricing-worker.js", { type: "module" });
button.addEventListener("click", () => {
const started = performance.now();
worker.postMessage({ items: window.bigPriceList });
worker.onmessage = (event) => {
totalBox.textContent = event.data.total.toFixed(2);
timeBox.textContent = `${Math.round(performance.now() - started)}ms`;
};
});
// pricing-worker.js
self.onmessage = (event) => {
const total = event.data.items.reduce((sum, item) => {
const afterDiscount = item.price * (1 - item.discount);
return sum + afterDiscount * 1.14;
}, 0);
self.postMessage({ total });
};لو شغلت نفس الحساب على الـ main thread، ممكن تشوف 840ms من UI blocking على جهاز متوسط. بعد النقل للـ worker، الحساب نفسه قد يظل قريبًا من 840ms، لكن الواجهة لا تتجمد بنفس الشكل. في قياس عملي بسيط، وقت الرسائل والتحديث النهائي ممكن يبقى حوالي 120ms بدل حجب كامل للواجهة.