Node.js Generatorق & قارن مع عمليات الاسترجاعات
⚡ ملخص ذكي
Generatorفي Node.js، تُعدّ الدوال التي يمكن إيقاف تنفيذها واستئنافها عند الطلب هي دوال يمكن إيقاف تنفيذها مؤقتًا واستئنافها عند الحاجة. تعمل الكلمة المفتاحية yield على إيقاف الدالة مؤقتًا، بينما تعمل الدالة next على استئنافها، وهذا الاقتران يُبسّط عمليات الاستدعاء المتداخلة بعمق إلى كود تسلسلي قابل للقراءة.

في هذا الشرح، سنلقي نظرة على المولدات وكيف تختلف عن عمليات الاستدعاء.
ما هي المولدات؟
Generatorأصبحت شائعة في Node.js بسبب ما هم قادرون على فعله.
- Generators هي عمليات تنفيذ وظيفة يمكن تعليقها واستئنافها في وقت لاحق.
- Generatorتُعد هذه مفيدة لمفاهيم مثل "التنفيذ الكسول". من خلال تعليق التنفيذ واستئنافه حسب الرغبة، يتم جلب القيم فقط عند الحاجة إليها.
Generatorلدينا الطريقتان الرئيسيتان التاليتان.
- طريقة العائد — يتم استدعاؤها داخل دالة لإيقاف التنفيذ عند السطر المحدد حيث
yieldيبدو. - الطريقة التالية يتم استدعاء هذه الدالة من التطبيق الرئيسي لاستئناف تنفيذ وظيفة توقفت مؤقتًا. ويستمر التنفيذ حتى الخطوة التالية.
yieldأو حتى نهاية الوظيفة.
لنلقِ نظرة على مثال لكيفية استخدام المولدات. يُعرّف المثال دالة جمع بسيطة تجمع عددين، ويتوقف التنفيذ عند نقاط مختلفة لإظهار كيفية عمل المولدات.
function* Add(x) { yield x + 1; // The value passed into the second next() call lands here var y = yield(null); return x + y; } var gen = Add(5); gen.next(); // runs to the first yield, returns { value: 6, done: false } gen.next(6); // resumes, y becomes 6, returns { value: 11, done: true }
Code تفسير
- الخطوة الأولى هي تعريف دالة المولد. لاحظ
*إضافة إلىfunctionالكلمة المفتاحية. تُسمى الدالةAddويأخذ مُعاملًاx. - استخدم
yieldالكلمة المفتاحية خاصة بالمولدات وتوقف الدالة مؤقتًا أثناء التنفيذ. يتوقف التنفيذ هنا حتىnext()تم استدعاؤه. - الثاني
yieldكلاهما يوقف الدالة ويستقبل قيمة: أيًا كانت القيمة التي تم تمريرها إلى ما يليnext()تصبح المكالمة قيمة لـy. - دعوة
gen.next()في المرة الأولى، يتم تشغيل الدالة حتى الأولىyield. الاتصالgen.next(6)يستأنفها، ويُسند 6 إلىyوالعودةx + y.
⚠️ تصحيح: تضمنت القائمة الأصلية السطر التالي y = 6 مباشرة بعد var y = yield(null);. هذا التعيين يحل محل أي قيمة أخرى next() الإمدادات، مما يُفقد المثال جدواه ويجعل الثاني yield لا معنى له. يتم الآن تمرير القيمة من خلال gen.next(6)وهذا هو الأسلوب الذي يتلقى به المولد البيانات من المتصل.
الاستدعاءات مقابل المولدات
Generators يحل مشكلة تُعرف باسم "جحيم الاستدعاءات". في بعض الأحيان، تصبح دوال الاستدعاء متداخلة بشكل كبير أثناء تطوير تطبيقات Node.js، مما يجعل من الصعب تتبعها.
هنا تبرز أهمية المولدات. ومن أكثر الأمثلة شيوعًا إنشاء دوال مؤقتة. يبني المثال أدناه دالة تأخير زمني بسيطة، ثم يستدعيها بتأخيرات قدرها 1000 و2000 و3000 مللي ثانية.
الخطوة 1) قم بتعريف دالة الاستدعاء مع رمز التأخير الزمني اللازم.
function Timedelay(ptime, callback) { setTimeout(function() { callback("Pausing for " + ptime); }, ptime); // the delay must use ptime, the parameter name }
Code تفسير
- دالة تسمى
Timedelayيتم إنشاؤه باستخدام معلمةptime، والذي يحمل عدد المللي ثانية للتوقف المؤقت. setTimeoutينتظر لعدد من المللي ثواني ثم يستدعي دالة الاستدعاء المقدمة.
⚠️ تصحيح: تم تمرير الكود الأصلي time كحجة ثانية لـ setTimeoutولكن لا يوجد متغير باسم time موجود — المعامل هو ptimeكما هو مكتوب، فإنه يطرح ReferenceError: time is not defined.
الخطوة 2) والآن، لننظر إلى الكود الذي يستخدم دوال الاستدعاء. لربط التأخيرات التي تبلغ 1000 و2000 و3000 مللي ثانية، تتداخل دوال الاستدعاء داخل بعضها البعض.
Timedelay(1000, function(message) { console.log(message); Timedelay(2000, function(message) { console.log(message); Timedelay(3000, function(message) { console.log(message); }); }); });
Code تفسير
Timedelayيتم استدعاؤها كدالة رد نداء بقيمة 1000.- داخل تلك الاستدعاءات،
Timedelayيتم استدعاؤه مرة أخرى مع 2000. - داخل ذلك،
Timedelayيتم استدعاؤها للمرة الثالثة بقيمة 3000.
⚠️ تصحيح: كل رد نداء في السجل الأصلي msg، ولكن اسم المعامل messageرمت الخطوط الثلاثة جميعها ReferenceError: msg is not defined.
من الكود أعلاه يمكنك أن ترى أنه يصبح أكثر فوضوية مع كل استدعاء إضافي - هذا التداخل هو ما يصفه جحيم الاستدعاءات.
الخطوة 3) والآن انظر إلى نفس السلوك مكتوبًا باستخدام المولدات. يُقرأ التسلسل على شكل ثلاثة خطوط مستقيمة بدلاً من ثلاثة مستويات من التداخل.
function* Messages() { console.log(yield Timedelay(1000)); console.log(yield Timedelay(2000)); console.log(yield Timedelay(3000)); }
Code تفسير
- يتم تعريف دالة مولدة لاستدعاء
Timedelayوظيفة. yieldيستخدم معTimedelayو1000 كقيمة للمعامل.yieldيتم استخدامه مرة أخرى مع 2000، ومرة أخرى مع 3000.
⚠️ تصحيح: كتب الأصل console,log(...) مع وجود فاصلة بدلاً من نقطة في الأسطر الثلاثة جميعها، وهو خطأ في بناء الجملة ويمنع تحليل الملف على الإطلاق.
كيفية تشغيل ملف Generator مع وظيفة العداء
يبدو المولد أعلاه رائعًا، لكنه لن يفعل أي شيء بمفرده. هذه هي التفاصيل التي تغفلها معظم الشروحات، وهي السبب في أن النسخة البسيطة قد تبدو سحرية. المولد عبارة عن آلة متوقفة مؤقتًا، ويجب أن يستمر شيء ما في الضغط على الزر. استدعاء Messages() يقوم فقط بإنشاء كائن المولد ويعود على الفور؛ لا يبدأ أي مؤقت ولا يتم طباعة أي شيء.
الجزء المفقود هو برنامج تشغيل: دالة صغيرة تستدعي next()ينتظر حتى ينتهي العمل غير المتزامن، ثم يستدعي next() ومرة أخرى مع النتيجة.
// Timedelay now hands its result back to the runner function Timedelay(ptime) { return function(resume) { setTimeout(function() { resume("Pausing for " + ptime); }, ptime); }; } // The runner drives the generator to completion function run(generatorFunction) { var it = generatorFunction(); function step(value) { var result = it.next(value); if (result.done) return; // result.value is the function returned by Timedelay result.value(step); } step(); } run(Messages);
ثلاثة أسباب تجعل هذا الأمر ناجحاً. أولاً، Timedelay لم يعد يستقبل دالة رد نداء مباشرة؛ بل يُعيد دالة يمكن للمُشغِّل استدعاؤها، مما يُعيد التحكم عند انتهاء المؤقت. ثانيًا، it.next(value) يُغذي هذا المولد المتوقف بالنتيجة السابقة، وهذا ما يسمح yield ليظهر على الجانب الأيمن من المهمة. ثالثًا، يتوقف العداء بمجرد result.done يصبح هذا صحيحًا، وبالتالي ينتهي التكرار بشكل طبيعي. هذا النمط التكراري هو بالضبط ما توفره مكتبات مثل co، وهو الآلية التي async/await ثم اندمجت لاحقاً في اللغة نفسها.
Generators مقابل async/await: أيهما تستخدم؟
Generatorكانت s هي الطريقة القياسية لكتابة التعليمات البرمجية غير المتزامنة المسطحة قبل async/await كانت موجودة، ويشترك الاثنان في نفس الفكرة الأساسية المتمثلة في إيقاف وظيفة ما مؤقتًا في منتصفها. وقد دعمت Node ذلك. async/await بشكل أصلي منذ الإصدار 7.6، مما يغير التوصية لأي شخص يبدأ عملاً جديداً اليوم.
| البعد | مولدات كهرباء | غير متزامن/انتظار |
|---|---|---|
| مطلوب عداء | نعم، مكتوبة من قبلك أو من قبل مكتبة | لا، إنها جزء لا يتجزأ من اللغة |
| كلمة "إيقاف مؤقت" | yield |
await |
| معالجة الخطأ | يجب على العداء إعادة توجيه الأخطاء | محاولة عادية للإمساك |
| يمكن إيقاف مؤقتًا | نعم، ويمكن استئنافها بشكل انتقائي. | لا، يكمل التشغيل |
| أفضل ل | التسلسلات الكسولة والتدفقات اللانهائية | العمل غير المتزامن العادي |
استعمل async/await بالنسبة للتدفق غير المتزامن في الكود الجديد، حيث أنه يزيل المُشغِّل ويعيد استخدام أسلوب try و catch العادي. احتفظ بالمولدات لما تجيده فقط: إنتاج القيم عند الحاجة، وتكرار التسلسلات الكبيرة أو اللانهائية، وأي حالة يقرر فيها المستهلك متى يتم حساب القيمة التالية بالضبط.
مشترك Generator الأخطاء وكيفية إصلاحها
تنشأ معظم مشاكل المولدات من نسيان أن الدالة تكون خاملة حتى يتم استدعاؤها. next()أو من أخطاء نحوية صغيرة حول علامة النجمة. كل عرض أدناه يحدد سببه وحله.
- لا يحدث شيء عند استدعاء المولد: استدعاء هذه الدالة يُرجع مُكرِّرًا، وليس نتيجة.
next()أو مررها إلى أحد العدائين. - خطأ في بناء الجملة عند استخدام عبارة yield: علامة النجمة مفقودة.
yieldلا يسري هذا إلا داخلfunction*. - تصل القيمة من الدالة next() غير مُعرَّفة: أول
next()لا يمكن للمكالمة أن تُسلّم قيمة، لأن المُولّد لم يصل إلىyieldبعد. أرسل البيانات من المكالمة الثانية فصاعدًا. - حلقة تكرار لا نهائية في برنامج تشغيل:
result.doneلا يتم التحقق منه مطلقا، لذلكnext()يتم استدعاء الدالة باستمرار بعد انتهاء العملية. يجب أن تعود الدالة دائمًا عند الانتهاء. - تختفي الأخطاء بصمت: لا يتم إرجاع أي رفض داخل الخطوة غير المتزامنة إلى المولد. قم بتمريره باستخدام
it.throw(err)بحيث يمكن لـ try و catch المكتوبة داخل المولد أن تراها وتتعامل معها بالفعل.




