البرمجة

المولدات في جافاسكربت: الفهم الشامل

المولدات (Generators) في جافاسكربت: فهم أعمق وآلية العمل

مقدمة

في عالم تطوير البرمجيات، تلعب جافاسكربت دورًا مهمًا كأداة رئيسية في بناء تطبيقات الويب التفاعلية. إحدى المزايا الرائعة التي تقدمها جافاسكربت هي المولدات (Generators)، وهي نوع خاص من الدوال التي تمثل إضافة حديثة نسبيًا إلى اللغة، والتي توفر طريقة مبتكرة للتعامل مع العمليات المتعاقبة أو غير المتزامنة (Asynchronous).

يتمثل الهدف الرئيسي من استخدام المولدات في جافاسكربت في توفير آلية مرنة وسهلة لتوليد القيم عبر الزمن. هذه المولدات هي دوال يمكن إيقافها في أي نقطة معينة (باستخدام الكلمة المفتاحية yield) وإعادة استئنافها لاحقًا، مما يتيح مرونة أكبر في تدفق البيانات، خاصة في الحالات التي تحتاج فيها العمليات إلى إتمام بعض المهام بشكل متسلسل أو غير متزامن.

ما هي المولدات في جافاسكربت؟

المولدات هي دوال خاصة في جافاسكربت تتميز بقدرتها على إيقاف تنفيذها وإعادة استئنافه. يتم ذلك من خلال استخدام الكلمة المفتاحية yield داخل الدالة. عندما يتم استدعاء دالة مولد، يتم تنفيذ الكود داخلها حتى يتم الوصول إلى كلمة yield. عند الوصول إليها، يقوم المولد بتخزين حالته وتوقف تنفيذه، مما يسمح بإرجاع قيمة مؤقتة إلى المستدعي. يمكن استئناف المولد لاحقًا من النقطة التي توقف عندها.

هذه الآلية توفر طريقة غير تقليدية للتعامل مع القيم أو العمليات التي تتم عبر الزمن، مثل التعامل مع البيانات غير المتزامنة أو الحسابات التي تحتاج إلى التوقف مؤقتًا قبل أن تستمر.

بنية المولدات في جافاسكربت

1. كيفية إنشاء مولد

يتم إنشاء المولدات في جافاسكربت باستخدام الدالة الخاصة مع الكلمة المفتاحية function*. هذه الكلمة المفتاحية هي ما يميز المولدات عن الدوال العادية. عندما يتم تعريف دالة باستخدام function*، فإنها تصبح مولدًا. على سبيل المثال:

javascript
function* myGenerator() { yield 1; yield 2; yield 3; }

في المثال أعلاه، myGenerator هي دالة مولد تُعيد ثلاث قيم بشكل متسلسل، كل واحدة في دورة زمنية منفصلة.

2. كيفية استدعاء المولد

لاستدعاء المولد، يجب استخدام next()، وهي الدالة الخاصة بكل مولد. يتم استدعاء next() بشكل متكرر للحصول على القيم الناتجة من المولد. عند استدعاء next()، يعيد المولد كائنًا يحتوي على خاصيتين: value و done.

  • value: تحتوي على القيمة التي تم إرجاعها بواسطة yield.

  • done: تحدد ما إذا كان المولد قد أكمل تنفيذ جميع التعليمات داخل دالته (true إذا انتهى و false إذا لم ينتهِ بعد).

javascript
const generator = myGenerator(); console.log(generator.next()); // { value: 1, done: false } console.log(generator.next()); // { value: 2, done: false } console.log(generator.next()); // { value: 3, done: false } console.log(generator.next()); // { value: undefined, done: true }

في هذا المثال، يقوم المولد بإرجاع ثلاث قيم قبل أن يكتمل التنفيذ.

3. استخدام yield

الكلمة المفتاحية yield هي عنصر رئيسي في المولدات. عند استخدام yield، يتم إيقاف تنفيذ المولد في تلك النقطة وإرجاع القيمة المحددة. عند استئناف المولد، يتم متابعة التنفيذ بعد yield.

javascript
function* countUpTo(max) { let count = 1; while (count <= max) { yield count; count++; } } const counter = countUpTo(5); console.log(counter.next().value); // 1 console.log(counter.next().value); // 2 console.log(counter.next().value); // 3 console.log(counter.next().value); // 4 console.log(counter.next().value); // 5

في المثال أعلاه، المولد countUpTo يعد الأرقام حتى يصل إلى الرقم المحدد بواسطة max.

4. استئناف المولد

يمكن استئناف المولد باستخدام next() بشكل متكرر. عند استئناف المولد بعد yield، يبدأ التنفيذ من حيث توقف المولد. إذا تم استئناف المولد حتى اكتماله، فإن done في الكائن الناتج سيكون true ولن تُعاد أي قيم أخرى.

javascript
const generator = countUpTo(3); console.log(generator.next().value); // 1 console.log(generator.next().value); // 2 console.log(generator.next().value); // 3 console.log(generator.next().value); // undefined (تم اكتمال المولد)

المولدات كأدوات غير متزامنة

أحد الاستخدامات الأكثر شيوعًا للمولدات هو التعامل مع العمليات غير المتزامنة. يمكن استخدام المولدات مع Promises لجعل الكود غير المتزامن أكثر قابلية للقراءة والفهم.

مثال على استخدام المولدات مع Promises

في هذا المثال، سنستخدم مولدًا لاسترداد بيانات غير متزامنة عبر Promises باستخدام مكتبة async/await:

javascript
function* fetchData() { const response1 = yield fetch('https://api.example.com/data1'); const data1 = yield response1.json(); const response2 = yield fetch('https://api.example.com/data2'); const data2 = yield response2.json(); return [data1, data2]; } function runGenerator(gen) { const iterator = gen(); function handleNext(value) { const next = iterator.next(value); if (!next.done) { next.value.then(handleNext); } else { console.log('All data fetched:', next.value); } } handleNext(); } runGenerator(fetchData);

في هذا المثال، يتم استخدام المولد لإجراء طلبات HTTP غير متزامنة عبر fetch. يقوم الكود بانتظار استجابة كل طلب باستخدام yield، مما يجعل تدفق البيانات أكثر تنظيمًا ويعطي مظهرًا يشبه البرمجة المتزامنة.

المولدات مقابل الوعود (Promises)

على الرغم من أن Promises توفر طريقة قوية للتعامل مع العمليات غير المتزامنة، إلا أن المولدات تقدم تجربة أكثر مرونة في بعض الحالات. قد يكون من الأسهل قراءة الكود باستخدام المولدات، خاصة عندما يكون لديك تسلسل من العمليات غير المتزامنة التي تحتاج إلى تنفيذها في ترتيب معين. حيث يمكنك ببساطة استخدام yield بين كل عملية من عمليات Promise.

المولدات والـ async/await

إحدى الطرق التي يمكن من خلالها دمج المولدات مع العمليات غير المتزامنة هي عن طريق تحويل المولد إلى دالة async باستخدام async/await. يمكن لهذه الطريقة أن تبسط الكود بشكل كبير وتجعله أكثر وضوحًا.

javascript
async function fetchDataAsync() { const response1 = await fetch('https://api.example.com/data1'); const data1 = await response1.json(); const response2 = await fetch('https://api.example.com/data2'); const data2 = await response2.json(); return [data1, data2]; }

الطريقة المذكورة أعلاه هي الأسلوب المفضل اليوم في جافاسكربت للتعامل مع العمليات غير المتزامنة، نظرًا لبساطتها وسهولة قراءتها. على الرغم من أن المولدات كانت في البداية الحل الفعّال في بعض السيناريوهات، إلا أن async/await قد جعلها أقل استخدامًا.

المولدات وفرق الأداء

تتمتع المولدات بميزة في بعض الحالات عندما يتعلق الأمر بالأداء مقارنة بالـ Promises العادية. حيث تتيح المولدات فحص القيم التي يتم توليدها بشكل متسلسل دون الحاجة إلى الانتظار لإتمام وعد معين. ومع ذلك، فإن الـ async/await في جافاسكربت تم تحسينه إلى درجة أن الفرق بين المولدات و Promises أصبح ضئيلًا في العديد من الحالات.

الختام

المولدات (Generators) في جافاسكربت تقدم طريقة مرنة وممتازة لإدارة العمليات المتسلسلة وغير المتزامنة. وعلى الرغم من أن async/await قد أصبح الأداة الأساسية لإدارة الوعود (Promises)، إلا أن المولدات تظل مفيدة في حالات معينة حيث يتم تنفيذ العمليات التي يمكن إيقافها واستئنافها بشكل متسلسل. في النهاية، يعتمد اختيارك بين المولدات و Promises على طبيعة التطبيق ومتطلبات الأداء التي تسعى لتحقيقها.