برمجة جافا سكريبت غير متزامنة (رد الاتصال ، الوعد ، RxJs)

مرحبا بالجميع. على اتصال Omelnitsky سيرجي. منذ وقت ليس ببعيد ، قمت بقيادة دفق برمجة تفاعلي حيث تحدثت عن عدم التزامن في JavaScript. اليوم أود أن أوجز هذه المادة.



لكن قبل أن نبدأ المادة الرئيسية ، نحتاج إلى عمل مادة تمهيدية. لذلك ، لنبدأ بالتعريفات: ما هو الكومة وقائمة الانتظار؟


الكومة هي مجموعة يتم استلام عناصرها وفقًا لمبدأ LIFO "الأخير في ، أولاً"


قائمة الانتظار هي مجموعة يتم استلام عناصرها وفقًا للمبدأ ("من يأتي أولاً ، ويخرج أولاً" FIFO)


حسنا ، دعنا نستمر.



جافا سكريبت هي لغة برمجة مفردة. هذا يعني أنه يحتوي على مؤشر ترابط واحد للتنفيذ ومكدس واحد يتم فيه وضع قائمة الانتظار لوظائف التنفيذ. لذلك ، في وقت ما ، يمكن لـ JavaScript إجراء عملية واحدة فقط ، في حين أن العمليات الأخرى ستنتظر دورها في المكدس حتى يتم استدعاؤها.


مكدس الاستدعاءات عبارة عن بنية بيانات تقوم ، بعبارات بسيطة ، بتسجيل معلومات حول المكان في البرنامج الذي نحن فيه. إذا ذهبنا إلى إحدى الوظائف ، فسنضع سجلاً عنها في أعلى الحزمة. عندما نعود من الوظيفة ، نقوم بسحب العنصر العلوي من المكدس ونجد أنفسنا حيث أطلقنا على هذه الوظيفة. هذا هو كل ما يمكن القيام به كومة. والآن سؤال مهم للغاية. كيف يعمل عدم التزامن في JavasScript؟



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


النظر في المثال التالي والقيام خطوة بخطوة "التنفيذ". انظر أيضًا إلى ما يحدث في النظام.


console.log('Hi'); setTimeout(function cb1() { console.log('cb1'); }, 5000); console.log('Bye'); 


1) حتى الآن ، لا شيء يحدث. وحدة تحكم المستعرض نظيفة ، مكدس المكالمة فارغ.



2) ثم يضاف الأمر console.log ('Hi') إلى مكدس الاستدعاءات.



3) ويتم تنفيذه



4) ثم console.log ('مرحبا') تتم إزالة من مكدس المكالمة.



5) انتقل الآن إلى الأمر setTimeout (دالة cb1 () {...}). يتم إضافته إلى مكدس الاستدعاء.



6) يتم تنفيذ الأمر setTimeout (function cb1 () {...}). ينشئ المستعرض جهاز ضبط وقت يمثل جزءًا من واجهة برمجة تطبيقات الويب. سوف يفعل العد التنازلي.



7) اكتمل أمر setTimeout (دالة cb1 () {...}) وتمت إزالته من مكدس الاستدعاءات.



8) تتم إضافة الأمر console.log ('Bye') إلى مكدس الاستدعاءات.



9) يتم تنفيذ الأمر console.log ('Bye').



10) تتم إزالة الأمر console.log ('Bye') من مكدس الاستدعاءات.



11) بعد مرور 5000 مللي ثانية على الأقل ، يخرج المؤقت ويضع رد الاتصال cb1 في قائمة انتظار رد الاتصال.



12) تأخذ حلقة الحدث c الدالة cb1 من قائمة انتظار رد الاتصال وتضعها في مكدس الاستدعاءات.



13) يتم تنفيذ وظيفة cb1 وتضيف console.log ('cb1') إلى مكدس الاستدعاءات.



14) يتم تنفيذ الأمر console.log ('cb1').



15) تتم إزالة الأمر console.log ('cb1') من مكدس الاستدعاءات.



16) تتم إزالة وظيفة cb1 من مكدس الاستدعاء.


ألقِ نظرة على مثال في الديناميات:



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


تطور الشفرة غير المتزامنة.


 a(function (resultsFromA) { b(resultsFromA, function (resultsFromB) { c(resultsFromB, function (resultsFromC) { d(resultsFromC, function (resultsFromD) { e(resultsFromD, function (resultsFromE) { f(resultsFromE, function (resultsFromF) { console.log(resultsFromF); }) }) }) }) }) }); 

لا يمكن تنفيذ البرمجة غير المتزامنة ، كما نعرفها في JavaScript ، إلا بالوظائف. يمكن تمريرها مثل أي متغير آخر إلى وظائف أخرى. لذلك ولدت النداءات. إنه رائع وممتع واستفزازي ، حتى يتحول إلى الحزن والشوق والحزن. لماذا؟ نعم ، كل شيء بسيط:


  • مع التعقيد المتزايد للشفرة ، يتحول المشروع بسرعة إلى كتل متداخلة غامضة بشكل متكرر - "call call hell".
  • خطأ في التعامل مع يمكن تفويتها بسهولة.
  • لا يمكنك إرجاع التعبيرات مع الإرجاع.

مع وعد ، أصبحت الأمور أفضل قليلاً.


 new Promise(function(resolve, reject) { setTimeout(() => resolve(1), 2000); }).then((result) => { alert(result); return result + 2; }).then((result) => { throw new Error('FAILED HERE'); alert(result); return result + 2; }).then((result) => { alert(result); return result + 2; }).catch((e) => { console.log('error: ', e); }); 

  • ظهرت سلاسل الوعد ، مما أدى إلى تحسين إمكانية قراءة التعليمات البرمجية
  • ظهرت طريقة تعويض اللون منفصلة
  • الآن يمكنك تشغيل بالتوازي باستخدام Promise.all
  • يمكننا حل عدم التزامن المتداخل مع عدم التزامن / الانتظار

لكن promis له حدوده. على سبيل المثال ، لا يمكن التراجع عن الوعد ، دون الرقص مع الدف ، والأهم من ذلك ، أنه يعمل بقيمة واحدة.


حسنًا ، لقد تناولنا البرمجة التفاعلية بسلاسة. هل انت متعب حسنًا ، الشيء الجيد هو أنه يمكنك الذهاب لإلقاء بعض النوارس والتفكير والعودة لقراءة المزيد. وسوف تستمر.


أساسيات البرمجة التفاعلية


البرمجة التفاعلية هي نموذج برمجة يركز على تدفق البيانات وانتشار التغيير. دعونا نلقي نظرة فاحصة على ما هو دفق البيانات.


 //     const input = ducument.querySelector('input'); const eventsArray = []; //      eventsArray input.addEventListener('keyup', event => eventsArray.push(event) ); 

تخيل أن لدينا حقل الإدخال. نقوم بإنشاء مجموعة ، ولكل مجموعة مفاتيح لحدث الإدخال ، سنقوم بحفظ الحدث في صفيفنا. في هذه الحالة ، أود أن أشير إلى أن مجموعتنا مرتبة حسب الوقت ، أي مؤشر الأحداث اللاحقة أكبر من فهرس الأحداث السابقة. هذا الصفيف هو نموذج دفق بيانات مبسط ، لكنه ليس دفقًا بعد. من أجل أن يسمى هذا المصفوفة بأمان دفقًا ، يجب أن يكون قادرًا على إبلاغ المشتركين بطريقة ما بأنه قد تلقى بيانات جديدة. لذلك نأتي إلى تعريف التدفق.


دفق البيانات


 const { interval } = Rx; const { take } = RxOperators; interval(1000).pipe( take(4) ) 


الدفق هو مجموعة من البيانات مرتبة حسب الوقت يمكن أن تشير إلى أن البيانات قد تغيرت. تخيل الآن مدى ملاءمة كتابة التعليمات البرمجية التي تحتاج إلى تشغيل العديد من الأحداث في أجزاء مختلفة من التعليمات البرمجية في إجراء واحد. نحن فقط نشترك في البث وسيبلغنا عندما تحدث التغييرات. ويمكن لمكتبة RxJs القيام بذلك.



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


لنلقِ نظرة على المفاهيم الأساسية لهذه المكتبة.


يمكن ملاحظتها ، مراقب ، منتج


يمكن ملاحظته هو النوع الأساسي الأول الذي سننظر فيه. تحتوي هذه الفئة على الجزء الأكبر من تطبيق RxJs. يرتبط مع دفق يمكن ملاحظته ، حيث يمكنك الاشتراك في استخدام طريقة الاشتراك.


تطبق الملاحظة آلية مساعدة لإنشاء التحديثات ، ما يسمى المراقب . ويسمى مصدر القيم للمراقب المنتج . يمكن أن يكون مصفوفة ، مكرر ، مقبس ويب ، نوع من الأحداث ، إلخ. لذلك يمكننا القول أن يمكن ملاحظتها هو موصل بين المنتج والمراقب.


يمكن ملاحظة ثلاثة أنواع من الأحداث التي يمكن ملاحظتها:


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

دعنا نرى التجريبي:



في البداية سنقوم بمعالجة القيم 1 و 2 و 3 وبعد 1 ثانية. سوف نحصل على 4 وننهي تدفقنا.


أفكار في الأذن

ثم أدركت أن الرواية كانت أكثر إثارة للاهتمام من الكتابة عنها. : د


اشتراك


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


أنواع الجداول


HOTCOLD
منتج خلق خارج يمكن ملاحظتهامنتج تم إنشاؤه داخل يمكن ملاحظتها
يتم نقل البيانات في وقت إنشاء الملاحظة.يتم الإبلاغ عن البيانات في وقت الاشتراك
بحاجة الى مزيد من المنطق لإلغاء الاشتراكينتهي الخيط من تلقاء نفسه
يستخدم اتصال واحد لكثيريستخدم علاقة فردية
جميع الاشتراكات لها نفس القيمة.الاشتراكات مستقلة
يمكن فقد البيانات إذا لم يكن هناك اشتراكيعيد إصدار جميع قيم التدفق لاشتراك جديد

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


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


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


بشكل عام ، يكون فهم شكل التدفقات معقدًا جدًا للمبتدئين ، ولكنه مهم.


مشغلي


 return this.http.get(`${environment.apiUrl}/${this.apiUrl}/trade_companies`) .pipe( tap(({ data }: TradeCompanyList) => this.companies$$.next(cloneDeep(data))), map(({ data }: TradeCompanyList) => data) ); 

يوفر المشغلون لنا القدرة على العمل مع الجداول. أنها تساعد في السيطرة على الأحداث التي تحدث في الملاحظة. سننظر في أكثرها شيوعًا ، ويمكن العثور على المشغلين بمزيد من التفصيل باستخدام الروابط الموجودة في المعلومات المفيدة.


مشغلي - من


نبدأ مع المشغل المساعد ل. يخلق يمكن ملاحظتها على أساس قيمة بسيطة.



مشغلي - مرشح



عامل تصفية عامل التصفية ، كما يوحي الاسم ، بتصفية إشارة الدفق. إذا عاد المشغل إلى حقيقة ، فانتقل إلى المزيد.


مشغلي - خذ



take - يأخذ قيمة عدد الانبعاثات ، وبعدها ينتهي الدفق.


مشغلي - debounceTime



debounceTime - يتجاهل القيم المنبعثة التي تقع في الفترة الزمنية المحددة بين بيانات الإخراج - بعد انقضاء الفاصل الزمني الذي ينبعث منه القيمة الأخيرة.


 const { Observable } = Rx; const { debounceTime, take } = RxOperators; Observable.create((observer) => { let i = 1; observer.next(i++); //     1000 setInterval(() => { observer.next(i++) }, 1000); //     1500 setInterval(() => { observer.next(i++) }, 1500); }).pipe( debounceTime(700), //  700     take(3) ); 


مشغلي - takeWhile



تنبعث القيم حتى تعيد takeWhile false ، وبعد ذلك سوف تقوم بإلغاء الاشتراك من الدفق.


 const { Observable } = Rx; const { debounceTime, takeWhile } = RxOperators; Observable.create((observer) => { let i = 1; observer.next(i++); //     1000 setInterval(() => { observer.next(i++) }, 1000); }).pipe( takeWhile( producer => producer < 5 ) ); 


مشغلي - الجمع بين أحدث


يشبه عامل الجمع بين أحدث إصدار إلى حد ما. فهو يجمع بين العديد من المواضيع في واحد. بعد أن يصدر كل مؤشر ترابط واحد على الأقل ، نحصل على القيم الأخيرة من كل في شكل صفيف. علاوة على ذلك ، بعد أي انبعاث من التدفقات مجتمعة ، فإنه سيعطي قيم جديدة.



 const { combineLatest, Observable } = Rx; const { take } = RxOperators; const observer_1 = Observable.create((observer) => { let i = 1; //     1000 setInterval(() => { observer.next('a: ' + i++); }, 1000); }); const observer_2 = Observable.create((observer) => { let i = 1; //     750 setInterval(() => { observer.next('b: ' + i++); }, 750); }); combineLatest(observer_1, observer_2).pipe(take(5)); 


مشغلي - الرمز البريدي


Zip - ينتظر قيمة من كل تيار ويشكل صفيفًا يستند إلى هذه القيم. إذا كانت القيمة لا تأتي من أي تيار ، فلن يتم تشكيل المجموعة.



 const { zip, Observable } = Rx; const { take } = RxOperators; const observer_1 = Observable.create((observer) => { let i = 1; //     1000 setInterval(() => { observer.next('a: ' + i++); }, 1000); }); const observer_2 = Observable.create((observer) => { let i = 1; //     750 setInterval(() => { observer.next('b: ' + i++); }, 750); }); const observer_3 = Observable.create((observer) => { let i = 1; //     500 setInterval(() => { observer.next('c: ' + i++); }, 500); }); zip(observer_1, observer_2, observer_3).pipe(take(5)); 


المشغلين - forkJoin


يقوم forkJoin أيضًا بتسلسل مؤشرات الترابط ، لكنه لا يُقدر إلا عند اكتمال جميع سلاسل العمليات.



 const { forkJoin, Observable } = Rx; const { take } = RxOperators; const observer_1 = Observable.create((observer) => { let i = 1; //     1000 setInterval(() => { observer.next('a: ' + i++); }, 1000); }).pipe(take(3)); const observer_2 = Observable.create((observer) => { let i = 1; //     750 setInterval(() => { observer.next('b: ' + i++); }, 750); }).pipe(take(5)); const observer_3 = Observable.create((observer) => { let i = 1; //     500 setInterval(() => { observer.next('c: ' + i++); }, 500); }).pipe(take(4)); forkJoin(observer_1, observer_2, observer_3); 


مشغلي - خريطة


يحول مشغل تحويل الخريطة قيمة الباعث إلى قيمة جديدة.



 const { Observable } = Rx; const { take, map } = RxOperators; Observable.create((observer) => { let i = 1; //     1000 setInterval(() => { observer.next(i++); }, 1000); }).pipe( map(x => x * 10), take(3) ); 


مشغلي - حصة ، انقر فوق


مشغل النقر - يسمح لك بالقيام بآثار جانبية ، أي أي إجراءات لا تؤثر على التسلسل.


يمكن لمشغل مشاركة المرافق أن يجعله ساخنًا من تيار بارد.



مع الانتهاء من المشغلين. دعنا ننتقل إلى الموضوع.


أفكار في الأذن

ثم ذهبت لشرب بعض النوارس. هذه الأمثلة حملتني: د


عائلة الموضوع


تعتبر عائلة الموضوعات مثالًا رئيسيًا على التدفقات الساخنة. هذه الفئات هي نوع من الهجين الذي يعمل في وقت واحد كما يمكن ملاحظتها ومراقب. نظرًا لأن موضوع البث المباشر ، فأنت بحاجة إلى إلغاء الاشتراك منه. إذا تحدثنا عن الأساليب الأساسية ، فإن هذا:


  • التالي - نقل بيانات جديدة إلى الدفق
  • خطأ - خطأ وإنهاء الدفق
  • الانتهاء الكامل من الدفق
  • الاشتراك - الاشتراك في الدفق
  • إلغاء الاشتراك - إلغاء الاشتراك من الدفق
  • asObservable - تحويل إلى مراقب
  • toPromise - يتحول إلى وعد

تفرز 4 5 أنواع من الموضوع.


أفكار في الأذن

لقد تحدث في الدفق 4 ، لكن اتضح أنهم أضافوا واحدًا آخر. كما يقول المثل ، عش وتعلم.


الموضوع البسيط الموضوع new Subject() هو أبسط نوع من الموضوع. يتم إنشاؤه دون المعلمات. يمر القيم التي جاءت فقط بعد الاشتراك.


BehaviorSubject new BehaviorSubject( defaultData<T> ) - في رأيي النوع الأكثر شيوعًا للموضوع. يقبل الإدخال قيمة افتراضية. يقوم دائمًا بحفظ بيانات آخر إصدار ينقله عند الاشتراك. تحتوي هذه الفئة أيضًا على طريقة قيمة مفيدة تقوم بإرجاع القيمة الحالية للدفق.


ReplaySubject new ReplaySubject(bufferSize?: number, windowTime?: number) - يمكن للمدخلات أن تقبل اختياريًا الوسيطة الأولى باعتبارها حجم المخزن المؤقت للقيم التي سيتم تخزينها في حد ذاتها ، والمرة الثانية التي نحتاج فيها إلى تغييرات.


AsyncSubject new AsyncSubject() - لا يحدث شيء عند الاشتراك ، وسيتم إرجاع القيمة فقط عند اكتمالها. سيتم إرجاع قيمة الدفق الأخير فقط.


WebSocketSubject new WebSocketSubject(urlConfigOrSource: string | WebSocketSubjectConfig<T> | Observable<T>, destination?: Observer<T>) - الوثائق غير صامتة حول هذا الموضوع ، وأرى ذلك للمرة الأولى. الذي يعرف ما يفعله ، والكتابة ، وملحق.


FWF. حسنًا ، لقد درسنا هنا كل ما أردت قوله اليوم. آمل أن تكون هذه المعلومات مفيدة. يمكنك التعرف على قائمة المراجع في علامة التبويب معلومات مفيدة.


معلومات مفيدة


Source: https://habr.com/ru/post/ar462355/


All Articles