الغرض من هذه المقالة هو توضيح سبب الحاجة إلى البرمجة التفاعلية ، وكيفية ارتباطها بالبرمجة الوظيفية ، وكيفية استخدامها لكتابة شفرة تعريفية يسهل تكييفها مع المتطلبات الجديدة. بالإضافة إلى ذلك ، أريد أن أقوم بذلك باختصار وببساطة قدر الإمكان بمثال قريب من الحقيقي.
قم بالمهمة التالية:
هناك خدمة معينة مع REST API ونقطة النهاية /people
. يؤدي طلب POST إلى نقطة النهاية هذه إلى إنشاء كيان جديد. اكتب دالة تأخذ مصفوفة كائنات من النموذج { name: 'Max' }
وقم بإنشاء مجموعة من الكيانات باستخدام واجهة برمجة التطبيقات (تسمى هذه بالإنجليزية عملية دفعية).
لنحل هذه المشكلة بأسلوب حتمي:
const request = require('superagent') function batchCreate(bodies) { const calls = [] for (let body of bodies) { calls.push( request .post('/people') .send(body) .then(r => r.status) ) } return Promise.all(calls) }
للمقارنة ، دعنا نعيد كتابة هذا الكود بأسلوب وظيفي. من أجل البساطة ، نعني بالأسلوب الوظيفي:
- استخدام البدائية الوظيفية ( .map ، .filter ، .reduce ) بدلاً من الحلقات الحتمية ( في حين ،)
- يتم تنظيم الشفرة في وظائف "خالصة" - فهم يعتمدون فقط على حججهم ولا يعتمدون على حالة النظام
كود النمط الوظيفي:
const request = require('superagent') function batchCreate(bodies) { const calls = bodies.map(body => request .post('/people') .send(body) .then(r => r.status) ) return Promise.all(calls) }
لقد حصلنا على قطعة من الرمز من نفس الحجم ومن الجدير الاعتراف أنه ليس من الواضح كيف أن هذه القطعة أفضل من السابقة.
لفهم سبب كون الجزء الثاني من الكود أفضل - تحتاج إلى البدء في تغيير الكود ، تخيل ظهور متطلبات جديدة للمهمة الأصلية:
الخدمة التي نطلق عليها حدًا لعدد الطلبات في فترة زمنية: في ثانية واحدة ، لا يمكن لعميل واحد تنفيذ أكثر من خمسة طلبات. سيؤدي تنفيذ المزيد من الطلبات إلى إرجاع الخدمة لخطأ 429 HTTP (عدد كبير جدًا من الطلبات).
في هذه المرحلة ، ربما يجدر التوقف ومحاولة حل المشكلة بنفسك ،٪ username٪
لنأخذ كودنا الوظيفي كأساس ونحاول تغييره. تكمن المشكلة الرئيسية للبرمجة الوظيفية "الخالصة" في أنها لا "تعرف" أي شيء - حول وقت التشغيل والإخراج - الإدخال (في اللغة الإنجليزية هناك تعبير عن الآثار الجانبية لهذا) ، ولكن في الممارسة العملية نحن نعمل معهم باستمرار.
لملء هذه الفجوة ، تأتي البرمجة التفاعلية لإنقاذها - مجموعة من الأساليب التي تحاول حل مشكلة الآثار الجانبية. أشهر تطبيق لهذا النموذج هو مكتبة Rx باستخدام مفهوم التدفقات التفاعلية .
ما هي التيارات التفاعلية؟ إذا كان ذلك لفترة وجيزة للغاية ، فهذا نهج يسمح لك بتطبيق البدائل الوظيفية (.map ، .filter ، .reduce) على شيء ما يتم توزيعه بمرور الوقت.
على سبيل المثال ، نرسل مجموعة معينة من الأوامر عبر الشبكة - لا نحتاج إلى الانتظار حتى نحصل على المجموعة بأكملها ، ونقدمها كتدفق تفاعلي ويمكننا العمل معها. يظهر هنا مفهومان مهمان آخران:
- يمكن أن يكون التدفق غير محدود أو يتم توزيعه بشكل تعسفي بمرور الوقت
- يرسل الجانب المرسل الأمر فقط إذا كان الجانب المستقبل جاهزًا لمعالجته (الضغط الخلفي)
الغرض من هذه المقالة هو العثور على طرق سهلة ، لذلك ، سنأخذ مكتبة Highland ، التي تحاول حل نفس المشكلة مثل Rx ، ولكن يسهل تعلمها كثيرًا. الفكرة في الداخل بسيطة: لنأخذ تدفقات Node.js كأساس و "نقل" البيانات من دفق إلى آخر.
لنبدأ: لنبدأ بواحد بسيط - سنجعل شفرتنا "تفاعلية" بدون إضافة وظائف جديدة
const request = require('superagent') const H = require('highland') function batchCreate(bodies) { return H(bodies) .flatMap(body => H(request .post('localhost:3000/people') .send(body) .then(r => r.status) ) ) .collect() .toPromise(Promise) }
ما يجب الانتباه إليه:
- H (جثث) - نقوم بإنشاء دفق من صفيف
- .flatMap والاستدعاء الذي يقبله. الفكرة بسيطة للغاية - نلف الوعد في مُنشئ دفق من أجل الحصول على دفق بقيمة واحدة (أو خطأ. من المهم أن نفهم أن هذه هي القيمة وليس الوعد).
ونتيجة لذلك ، يمنحنا هذا تدفقًا من التدفقات - باستخدام FlatMap ، نجعلها ناعمة في تيار واحد من القيم التي يمكننا العمل عليها (من قال monad؟) - .collect - نحتاج إلى جمع كل القيم في "نقطة" واحدة في صفيف
- .toPromise - سيعود إلينا وعدًا ، والذي سيتم الوفاء به في الوقت الذي نمتلك فيه قيمة من الدفق
الآن دعنا نحاول تنفيذ متطلباتنا:
const request = require('superagent') const H = require('highland') function batchCreate(bodies) { return H(bodies) .flatMap(body => H(request .post('localhost:3000/people') .send(body) .then(r => r.status) ) ) .ratelimit(5, 1000) .collect() .toPromise(Promise) }
بفضل مفهوم الضغط العكسي ، هذا مجرد سطر واحد. حد أدنى في هذا النموذج. في Rx ، يستغرق الأمر نفس المساحة تقريبًا .
حسنًا هذا كل شيء ، رأيك مثير للاهتمام:
- هل تمكنت من تحقيق النتيجة المعلنة في بداية المقال؟
- هل من الممكن تحقيق نتيجة مماثلة باستخدام نهج حتمي؟
- هل أنت مهتم بالبرمجة التفاعلية؟
ملاحظة: هنا يمكنك العثور على مقال آخر خاص بي حول البرمجة التفاعلية