انتقل إلى المحتوى
في هذه الصفحة

سلسلة RxJS - المراقبون

سلسلة RxJS

RxJS (على Github) هو مكتبة للبرمجة التفاعلية لـ Javascript، وفي هذه السلسلة، أود أن أشارككم طريقة تعلمي لها.

من ويكيبيديا :

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

من توثيق Angular:

البرمجة التفاعلية هي نموذج برمجة غير متزامن يتعلق بالتدفقات البياناتية وانتشار التغييرات. RxJS (الامتدادات التفاعلية لـ JavaScript) هي مكتبة للبرمجة التفاعلية باستخدام المراقبات تسهل تجميع الكود غير المتزامن أو المبني على ردود النداء.

سأتناول أولاً اللبنات الأساسية ثم المشغّلات الأكثر استخداماً.

اللبنات الأساسية

Observable & Observer & Subscriber

مفهوم المراقبين والمراقبين متصل ببعضهم، حيث لا يوجد مراقب يعمل بدون مراقب.

لنفحص التوثيق أولاً:

المراقب هو (من التوثيق):

واجهة لمستهلك الإشعارات المُpushية التي يُسليمها المراقب.

المشترك هو (من التوثيق):

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

المراقب هو (من التوثيق):

تمثيل لأي مجموعة من القيم على أي مدة زمنية. هذه هي اللبنة الأساسية الأكثر أساسية لـ RxJS.

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

العودة للأعلى

من الصفر

يبدو الكود المصدري للمراقب هكذا:

interface Observer<T> {
  closed?: boolean;
  next: (value: t) => void;
  error: (err: any) => void;
  complete: () => void;
}

يُنفذ ثلاثة طرق:

  • next: رد نداء لتلقي إشعارات من نوع next
  • error: رد نداء لتلقي إشعارات من نوع error
  • complete: رد نداء لتلقي إشعارات من نوع complete

يمكن للمراقب ببساطة دفع الإشعارات إلى المراقب في كل مرة يرغبها، أي يمكن تنفيذ دالة تقوم بذلك:

function customObservable(observer: Observer<T>){
  observer.next(1);
  observer.next(2);
  observer.complete();
}

إذا استدعنا customObservable الخاص بنا مع مراقب كمعامل:

customObservable({
  next: val => console.log(val),
  error: err => console.error(err),
  complete: () => console.log('Completed')
});

ستبدو النتيجة هكذا:

1
2
Completed

هذه شكل أساسي للمراقب، ولكن لو كان بهذه البساطة، لماذا نحتاج إطار عمل كامل؟ الإجابة بسيطة، جربوا استدعاء next بعد complete:

function customObservable(observer: Observer<T>){
  observer.next(1);
  observer.next(2);
  observer.complete();
  observer.next(3);
}

ستبدو النتيجة هكذا:

1
2
Completed
3

هذا لا يعمل بالطريقة المطلوبة فعلاً.

(جرب على StackBlitz)

لنأخذ مثالاً أكثر تعقيداً، حيث يُغلّف المراقب تدفق أرقام على فترات زمنية:

function customObservable(observer: Observer<T>){
  let counter = 1;
  const out = setInterval(() => observer.next(counter++), 1000);
  return () => {
    console.log('done');
    clearInterval(out);
  };
}
const streamObservale = customObservable({
  next: val => console.log(val),
  error: err => console.error(err),
  complete: () => console.log('Completed')
});
setTimeout(() => {
  streamObservale();
  }, 3100);

ستظهر النتيجة قيمة واحدة كل ثانية:

1
2
3
done

(جرب على StackBlitz)

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

يُعرض العلاقة بين المراقب والمراقب في الرسوم المتحركة التالية:

رسوم متحركة

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

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

طالما لديكم أكثر من مجموعة أو تدفق من القيم، يمكنكم تخيّل كم من العمليات قد تحتاجون تطبيقها عليها. يوفر RxJS طرقاً عديدة لإنشاء مراقب مع جميع ميزات الأمان وغير المتزامنة التي قد تتمناها.

إنشاء مراقب باستخدام الطرق الثابتة لـ RxJS

- of:

عن طريق تغليف تسلسل من القيم بـ of يتم إرجاع مراقب بتلك القيم حيث يُصدر القيم في التسلسل كما هي معطاة، ثم يكتمل.

import { of } from 'rxjs';

const observableOf = of(1, 2, 3); // an observable of the values 1,2,3

observableOf.subscribe(value => console.log(value));

العودة للأعلى (جرب على StackBlitz)

- range:

هذا يُذكّرني بدالة range في Python، يُصدر تسلسل أرقام يُحدد بمعاملات النطاق (start, count).

import { range } from 'rxjs';

const source = range(10, 5); // 10, 11, 12, 13, 14

source.subscribe(x => console.log(x));

العودة للأعلى (جرب على StackBlitz)

- interval:

يأخذ هذا معاماً كوقت بالمللي ثانية ويُولّد تسلسل أرقام في كل فترة زمنية. بهذه الطريقة تحصلون على سلسلة زمنية لا نهائية من الأرقام.

import { interval } from 'rxjs';

const source = interval(1000); // every 1 second

source.subscribe(x => console.log(x));

العودة للأعلى (جرب على StackBlitz)

- timer:

يأخذ هذا معامين (initialDelay, period). يمكن أن يكون التأخير الأولي رقماً يمثل الوقت بالمللي ثانية، أو كائن Date. ينتظر المراقب قيمة التأخير الأولي، ثم يبدأ بإصدار أرقام تبدأ من 0 في كل فترة زمنية.

import { timer } from 'rxjs';

const source = timer(2000, 3000); // start after 2 second, then every 3 seconds

source.subscribe(x => console.log(x));

العودة للأعلى (جرب على StackBlitz)

- from:

هذه طريقة سحرية، تحوّل الكثير من أنواع البيانات إلى مراقبين: المصفوفة، ما يشبه المصفوفة، الوعاء، الكائن القابل للتكرار، النص (كمصفوفة من الأحرف).

import { from } from 'rxjs';
import { take } from 'rxjs/operators';

const fromArray = from([1, 2, 3]); // observable of the values 1,2,3
fromArray.subscribe(value => console.log(value));

// infinite iterable of values
function* generateNumbers(seed) {
  let i = seed;
  while(true) {
    yield i;
    i *= 2;
  }
}
const iter = generateNumbers(100);
const fromGenerator = from(iter).pipe(take(5)); // 100, 200, 400, 800, 1600
fromGenerator.subscribe(value => console.log(value));

// from a promise
const promise =  new Promise((resolve, reject) => {
  resolve(true);
  reject(false);
});
const source = from(promise);
source.subscribe(x => console.log(x));

العودة للأعلى (جرب على StackBlitz) >> يُستخدم مشغّل take هنا لإظهار أول 5 قيم من التدفق اللانهائي، سيتم مناقشته بالتفصيل لاحقاً

- fromEvent:

هذه الطريقة مفيدة أثناء العمل مع DOM، لأنها تنشئ مراقبين من أحداث DOM أو أحداث EventEmitter في Node.js. تأخذ معامين (element: EventTarget, eventName: string)

import { fromEvent } from 'rxjs';

const source = fromEvent(document, 'click'); // capture click events

// show mouse coordinates
source.subscribe(point => console.log(`(${point.clientX}, ${point.clientY})`));

العودة للأعلى (جرب على StackBlitz)

- create:

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

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

import { Observable } from 'rxjs'; 

const source = Observable.create((observer) => {
  let id = 0; // setting up a counter
  setInterval(() => {
    observer.next(id++);
  }, 1000);

  return () => { id = 0; console.log('Counter cleared');}
})

const sub = source.subscribe(val => console.log(val));

// unsubscribe after 4.5 sec
setTimeout(() => {
  sub.unsubscribe();
}, 4500);

العودة للأعلى (جرب على StackBlitz)

الخاتمة

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

حتى المقال التالي في المدونة!


مزيد من المراجع حول المراقبين:

العودة للأعلى