خيط واحد يحكم الجميع: الخرافات والحقيقة حول التنافسية في جافا سكريبت
\n\nمنذ زمن بعيد، في عصر المواقع الثابتة وأزرار "إرسال" التي كانت تحجب الواجهة بأكملها، كانت جافا سكريبت تعيش بسعادة في خيط واحد. كان ذلك كافياً لها: يأتي طلب - يُنفذ، يذهب طلب - تتجمد الواجهة. لكن الأوقات تغيرت. اليوم، نتوقع من تطبيقات الويب ردود فعل فورية: التمرير يجب ألا يتباطأ، والرسوم المتحركة يجب أن تكون سلسة، وتحميل البيانات يجب أن يكون غير ملحوظ. وهنا تظهر التنافسية في اللعبة.
\n\nكثير من المطورين يخلطون بين التنافسية (concurrency) والتوازي (parallelism). في جافا سكريبت، خاصة في المتصفح، نتعامل غالباً مع الأولى. هذا مثل المشعوذ: لا يحمل كل الكرات في الهواء في نفس الوقت (هذا هو التوازي)، لكنه ينتقل بينها ببراعة لدرجة أنه يخلق وهم التزامن. في هذه المقالة، سنشرح ثلاث أدوات رئيسية لتنظيم التنافسية في JS: async/await، الغوروتينات (عبر المولدات و Web Workers) والخيوط التقليدية. هيا بنا!
Async/Await: كود غير متزامن أنيق
\n\nasync/await هو سكر نحوي فوق الوعود (Promises)، ظهر في ES2017. لا يجعل الكود متعدد الخيوط، لكنه يجعله غير متزامن. عندما تكتب await، "تغفو" جافا سكريبت في هذه الدالة، محررة الخيط لمهام أخرى. بمجرد أن يتم حل الوعد، تستيقظ الدالة وتستمر في التنفيذ. هذه أداة مثالية لعمليات الإدخال/الإخراج: طلبات API، قراءة الملفات، المؤقتات.
مثال بسيط: تحميل بيانات من الخادم
\n\n// دالة ترجع Promise\nfunction fetchUser(id) {\n return new Promise((resolve) => {\n setTimeout(() => resolve({ id, name: 'أليكسي' }), 1000);\n });\n}\n\n// دالة غير متزامنة\nasync function loadUserProfile(userId) {\n console.log('نبدأ التحميل...');\n const user = await fetchUser(userId);\n console.log('تم تحميل المستخدم:', user.name);\n return user;\n}\n\nloadUserProfile(42);\nconsole.log('هذا الكود سيُنفذ فوراً دون انتظار التحميل!');\n\n\nلاحظ: console.log('هذا الكود سيُنفذ فوراً...') سيعمل قبل console.log('تم تحميل المستخدم'). الخيط لا يُحجب - هذه هي التنافسية في العمل.
معالجة الأخطاء
\n\nasync function fetchWithRetry(url, retries = 3) {\n for (let i = 0; i < retries; i++) {\n try {\n const response = await fetch(url);\n if (!response.ok) throw new Error('خطأ HTTP');\n return await response.json();\n } catch (error) {\n if (i === retries - 1) throw error;\n console.log(`المحاولة ${i + 1} فشلت، نحاول مرة أخرى...`);\n }\n }\n}\n\n\nمتى تستخدم: أي عمليات مع موارد خارجية (API، قواعد بيانات، نظام الملفات في Node.js). async/await هو أداتك الرئيسية للكود غير المتزامن.
الغوروتينات في جافا سكريبت: خرافة أم حقيقة؟
\n\nمصطلح "غوروتين" جاء من Go ويشير إلى خيط تنفيذ خفيف الوزن. في جافا سكريبت، لا توجد غوروتينات أصلية، لكن هناك مولدات (Generator Functions) وWeb Workers يمكنها محاكاة سلوك مماثل. تسمح المولدات بإيقاف واستئناف تنفيذ الدالة، مما يعطي تحكماً في تدفق التنفيذ.
\n\nالمولدات كأساس للتنافسية
\n\nfunction* taskGenerator() {\n console.log('بدء المهمة 1');\n yield 'المهمة 1 اكتملت';\n console.log('بدء المهمة 2');\n yield 'المهمة 2 اكتملت';\n console.log('بدء المهمة 3');\n yield 'المهمة 3 اكتملت';\n}\n\nconst iterator = taskGenerator();\nconsole.log(iterator.next().value); // ينفذ المهمة 1\n// في هذه اللحظة يمكن فعل شيء آخر\nconsole.log(iterator.next().value); // ينفذ المهمة 2\nconsol