مقدمة: لماذا نحتاج إلى قوائم المهام في JavaScript؟
\nفي تطوير الويب الحديث باستخدام Node.js، غالبًا ما تظهر مهام لا يمكن تنفيذها فورًا ضمن طلب HTTP واحد. إرسال البريد الإلكتروني الجماعي، إنشاء تقارير PDF، معالجة الصور، المزامنة مع واجهات برمجة التطبيقات الخارجية - كل هذه العمليات تتطلب وقتًا وموارد. إذا تم تنفيذها بشكل متزامن، فسينتظر المستخدم الرد لفترة طويلة جدًا، وقد يصبح الخادم مثقلًا.
\nقوائم المهام (Job Queues) تحل هذه المشكلة. فهي تسمح بتأجيل تنفيذ العمليات الثقيلة، توزيع الحمل، ضمان إعادة المعالجة عند حدوث أعطال، وتوسيع النظام أفقيًا. في هذه المقالة، سنشرح الجوانب العملية للعمل مع قوائم المهام في JavaScript (Node.js): من النظرية إلى كتابة كود عملي باستخدام المكتبات الشائعة.
\n\nالمفاهيم الأساسية لقوائم المهام
\nقبل الانتقال إلى الكود، من المهم فهم العناصر الرئيسية لأي قائمة مهام:
\n- \n
- Job (مهمة) - وحدة العمل التي يجب تنفيذها. تحتوي عادةً على بيانات (payload) ومعلومات وصفية (أولوية، محاولات، حالة). \n
- Queue (قائمة انتظار) - هيكل بيانات (غالبًا يعتمد على Redis) يخزن المهام بترتيب وصولها. \n
- Worker (عامل) - عملية أو خيط يسحب المهام من قائمة الانتظار وينفذها. \n
- Producer (منتج) - جزء النظام الذي يضيف المهام إلى قائمة الانتظار. \n
- Consumer (مستهلك) - مرادف للعامل الذي يعالج المهام. \n
في Node.js، أشهر المكتبات للعمل مع قوائم المهام هي Bull و Kue. كلاهما يستخدم Redis كوسيط للرسائل. Bull أكثر حداثة وأداءً، بينما Kue أبسط للبدء. في هذه المقالة، سنركز على Bull كحل موصى به للإنتاج.
\n\nتنفيذ عملي لقائمة انتظار باستخدام Bull
\nالتثبيت والإعداد
\nللبدء، قم بتثبيت Redis (محليًا أو عبر Docker) وأضف المكتبة إلى المشروع:
\nnpm install bull\nالتهيئة الأساسية لقائمة الانتظار تبدو كالتالي:
\nconst Queue = require('bull');\nconst imageQueue = new Queue('معالجة الصور', {\n redis: { host: 'localhost', port: 6379 }\n});\n\nإضافة المهام (Producer)
\nالمنتج يضيف مهمة مع البيانات والخيارات:
\napp.post('/upload', async (req, res) => {\n const job = await imageQueue.add({\n imageUrl: req.body.url,\n userId: req.user.id\n }, {\n attempts: 3, // عدد المحاولات عند الخطأ\n backoff: 5000, // التأخير بين المحاولات (مللي ثانية)\n removeOnComplete: true // حذف المهمة بعد النجاح\n });\n res.json({ jobId: job.id });\n});\n\nمعالجة المهام (Worker)
\nالعامل هو عملية منفصلة (أو نفس العملية ولكن غير متزامنة) تستمع إلى قائمة الانتظار:
\nimageQueue.process(async (job, done) => {\n try {\n const { imageUrl, userId } = job.data;\n console.log(`معالجة الصورة ${imageUrl} للمستخدم ${userId}`);\n // هنا منطقك: تحميل، تغيير الحجم، حفظ\n await processImage(imageUrl);\n done(); // إشارة اكتمال ناجح\n } catch (error) {\n done(new Error('خطأ في المعالجة: ' + error.message));\n }\n});\n\nمعالجة الأخطاء وإعادة المحاولة
\nيقوم Bull تلقائيًا بإعادة محاولة المهمة إذا انتهى العامل بخطأ. يمكنك تخصيص استراتيجية إعادة المحاولة:
\nconst job = await imageQueue.add(data, {\n attempts: 5,\n backoff: { type: 'exponential', delay: 2000 }\n});\nيمكنك أيضًا الاستماع إلى أحداث قائمة الانتظار للمراقبة:
\nimageQueue.on('completed', (job, result) => {\n console.log(`المهمة ${job.id} اكتملت`);\n});\nimageQueue.on('failed', (job, err) => {\n console.error(`المهمة ${job.id} فشلت: ${err.mes