Node.js Generatorق & قارن مع عمليات الاسترجاعات

⚡ ملخص ذكي

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

  • ⏸️ السلوك الأساسي: يتوقف المولد عند كل نقطة توقف ويستأنف عمله من حيث توقف بالضبط عند استدعاء الأمر التالي.
  • ✳️ إعلان: تشير علامة النجمة بعد كلمة function إلى أن الدالة هي دالة مولدة وليست دالة عادية.
  • 🔄 قناة اتصال ثنائية الاتجاه: تصبح القيمة التي تم تمريرها إلى next نتيجة لتعبير yield الذي أوقف الدالة مؤقتًا.
  • 🪆 جحيم المكالمات المتكررة: تصبح عمليات الاستدعاء المتداخلة غير قابلة للقراءة مع إضافة الخطوات، وهي المشكلة التي تم اعتماد المولدات لحلها.
  • 🏃 مطلوب عداء: لا يقوم المولد بتشغيل نفسه؛ يجب على دالة التشغيل استدعاء الدالة التالية بشكل متكرر حتى يستمر العمل غير المتزامن.
  • 🦥 التنفيذ الكسول: تتيح عملية التعليق والاستئناف للبرنامج سحب القيم فقط في اللحظة التي تكون فيها مطلوبة.

Node.js
 Generatorمقارنة مع ردود الاتصال

في هذا الشرح، سنلقي نظرة على المولدات وكيف تختلف عن عمليات الاستدعاء.

ما هي المولدات؟

Generatorأصبحت شائعة في Node.js بسبب ما هم قادرون على فعله.

  • Generators هي عمليات تنفيذ وظيفة يمكن تعليقها واستئنافها في وقت لاحق.
  • Generatorتُعد هذه مفيدة لمفاهيم مثل "التنفيذ الكسول". من خلال تعليق التنفيذ واستئنافه حسب الرغبة، يتم جلب القيم فقط عند الحاجة إليها.

Generatorلدينا الطريقتان الرئيسيتان التاليتان.

  1. طريقة العائد — يتم استدعاؤها داخل دالة لإيقاف التنفيذ عند السطر المحدد حيث yield يبدو.
  2. الطريقة التالية يتم استدعاء هذه الدالة من التطبيق الرئيسي لاستئناف تنفيذ وظيفة توقفت مؤقتًا. ويستمر التنفيذ حتى الخطوة التالية. yieldأو حتى نهاية الوظيفة.

لنلقِ نظرة على مثال لكيفية استخدام المولدات. يُعرّف المثال دالة جمع بسيطة تجمع عددين، ويتوقف التنفيذ عند نقاط مختلفة لإظهار كيفية عمل المولدات.

Node.js
 Generators

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 تفسير

  1. الخطوة الأولى هي تعريف دالة المولد. لاحظ * إضافة إلى function الكلمة المفتاحية. تُسمى الدالة Add ويأخذ مُعاملًا x.
  2. استخدم yield الكلمة المفتاحية خاصة بالمولدات وتوقف الدالة مؤقتًا أثناء التنفيذ. يتوقف التنفيذ هنا حتى next() تم استدعاؤه.
  3. الثاني yield كلاهما يوقف الدالة ويستقبل قيمة: أيًا كانت القيمة التي تم تمريرها إلى ما يلي next() تصبح المكالمة قيمة لـ y.
  4. دعوة 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) قم بتعريف دالة الاستدعاء مع رمز التأخير الزمني اللازم.

الاسترجاعات مقابل. Generators

function Timedelay(ptime, callback) {

    setTimeout(function() {

        callback("Pausing for " + ptime);

    }, ptime);   // the delay must use ptime, the parameter name
}

Code تفسير

  1. دالة تسمى Timedelay يتم إنشاؤه باستخدام معلمة ptime، والذي يحمل عدد المللي ثانية للتوقف المؤقت.
  2. setTimeout ينتظر لعدد من المللي ثواني ثم يستدعي دالة الاستدعاء المقدمة.

⚠️ تصحيح: تم تمرير الكود الأصلي time كحجة ثانية لـ setTimeoutولكن لا يوجد متغير باسم time موجود — المعامل هو ptimeكما هو مكتوب، فإنه يطرح ReferenceError: time is not defined.

الخطوة 2) والآن، لننظر إلى الكود الذي يستخدم دوال الاستدعاء. لربط التأخيرات التي تبلغ 1000 و2000 و3000 مللي ثانية، تتداخل دوال الاستدعاء داخل بعضها البعض.

الاسترجاعات مقابل. Generators

Timedelay(1000, function(message) {

    console.log(message);

    Timedelay(2000, function(message) {

        console.log(message);

        Timedelay(3000, function(message) {

            console.log(message);
        });
    });
});

Code تفسير

  1. Timedelay يتم استدعاؤها كدالة رد نداء بقيمة 1000.
  2. داخل تلك الاستدعاءات، Timedelay يتم استدعاؤه مرة أخرى مع 2000.
  3. داخل ذلك، Timedelay يتم استدعاؤها للمرة الثالثة بقيمة 3000.

⚠️ تصحيح: كل رد نداء في السجل الأصلي msg، ولكن اسم المعامل messageرمت الخطوط الثلاثة جميعها ReferenceError: msg is not defined.

من الكود أعلاه يمكنك أن ترى أنه يصبح أكثر فوضوية مع كل استدعاء إضافي - هذا التداخل هو ما يصفه جحيم الاستدعاءات.

الخطوة 3) والآن انظر إلى نفس السلوك مكتوبًا باستخدام المولدات. يُقرأ التسلسل على شكل ثلاثة خطوط مستقيمة بدلاً من ثلاثة مستويات من التداخل.

الاسترجاعات مقابل. Generators

function* Messages() {
    console.log(yield Timedelay(1000));
    console.log(yield Timedelay(2000));
    console.log(yield Timedelay(3000));
}

Code تفسير

  1. يتم تعريف دالة مولدة لاستدعاء Timedelay وظيفة.
  2. yield يستخدم مع Timedelay و1000 كقيمة للمعامل.
  3. 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 المكتوبة داخل المولد أن تراها وتتعامل معها بالفعل.

الأسئلة الشائعة

كائن له خاصيتان: value، محتفظين بكل ما تم تسليمه، و doneقيمة منطقية تُصبح صحيحة بمجرد انتهاء المُولِّد. يتم إجراء اختبارات التشغيل لمعرفة متى يجب التوقف.

نعم. المولد الذي يحتوي على حلقة لا نهائية آمن لأنه لا يتم إجراء أي عملية حسابية حتى يتم استدعاء الدالة next(). وهذا يجعل المولدات مناسبة تمامًا لتدفقات المعرفات أو النتائج المُقسّمة إلى صفحات.

نعم. AI تقوم الأدوات باستبدال yield بـ await وتتخلى عن المُشغِّل تمامًا. Revانظر إلى التسلسلات الكسولة بعناية، لأن المولد الذي لا ينتهي أبداً ليس له مكافئ مباشر لـ async await.

مساعدي منظمة العفو الدولية tracسواء توقف المُشغّل عن استدعاء الدالة التالية، أو لم يتم استدعاء دالة استئناف، أو لم يتم الوصول إلى مرحلة الاكتمال. هذه الأسباب الثلاثة تُفسر تقريباً كل حالات توقف المُولّد.

لا. تم تعريف مولد غير متزامن async function* ويستهلك مع for await...ofفهو يجمع بين كلا الفكرتين ويناسب تدفق البيانات غير المتزامنة مثل مؤشرات قواعد البيانات.

تلخيص هذه التدوينة بـ: