rxjs source trace

May 30, 2020

achieve

此篇內容做為個人學習記錄,不會再更改。

學習 haskell後,發現更重要的其實是函式編程的思考方式。

如果真的理解函式做為一級公民的思考邏輯, rxjs 的實現看起來就會非常自然、直覺。

當初寫這篇學習記錄時,並不懂fp的思考方式。

因此,下面的記錄比較像是逐行記錄動作、及 ts特性,並沒有真正解釋 rxjs 的實作目的。

2020/09/16


目的:看懂RxjsObservable實現方式

Rxjs的流程圖

Rxjs是一個實作了 觀察者模式疊代器模式的library。

根據 ReactiveX 官網所述:

Observables fill the gap by being the ideal way to access asynchronous sequences of multiple items

# single items multiple items
synchronus T getData() Iterable<T> getData()
asynchronous Future<T> getData() Observable<T> getData()

ReactiveX 填補了疊代器模式無法支援非同步旨令的問題。

事實上,我在理解原始碼的時候有發現,雖然Rxjs的確有實現 觀察者模式,但是當你在看程式碼的時候,將它當作一個變型的疊代器模式,反而更容易理解。

那麼我們就先從一個流程圖及簡單的範例開始吧。

以下是我們使用 Rxjs的時候,最典型的流程。

流程圖


我直接從 Anuglar 官網引用簡單的範例:

// Create simple observable that emits three values
const myObservable = of(1, 2, 3);

// Create observer object
const myObserver = {
  next: x => console.log('Observer got a next value: ' + x),
  error: err => console.error('Observer got an error: ' + err),
  complete: () => console.log('Observer got a complete notification'),
};

// Execute with the observer object
myObservable.subscribe(myObserver);
// Logs:
// Observer got a next value: 1
// Observer got a next value: 2
// Observer got a next value: 3
// Observer got a complete notification
  • code 1 使用 of 運算子產生 Observable 並訂閱。

另外,如果你不想要使用 Operator 建立 Observable的話,你可以直接 new一個 Obsevrable 實例,並且將訂閱後會發出值使用 callback function 的方式傳入 Observable 中。

實務上是不會這麼使用的。在實務上,可以使用 of這類的 creation operator 產生一個最簡單的Observable。而其內部的實現方式,其實就是回傳一個會固定發出 of 參數的 Observable

// This function runs when subscribe() is called
function sequenceSubscriber(observer) {
  // synchronously deliver 1, 2, and 3, then complete
  observer.next(1);
  observer.next(2);
  observer.next(3);
  observer.complete();

  // unsubscribe function doesn't need to do anything in this
  // because values are delivered synchronously
  return {unsubscribe() {}};
}

// Create a new Observable that will deliver the above sequence
const sequence = new Observable(sequenceSubscriber);

// execute the Observable and print the result of each notification
sequence.subscribe({
  next(num) { console.log(num); },
  complete() { console.log('Finished sequence'); }
});

// Logs:
// 1
// 2
// 3
// Finished sequence

code 2 建立 一個 Observable 物件。

這樣我們已經知道最基本的使用方式了。接著我們看看 Rxjs 中最核心的兩個組件:ObservableObserver

Observable

Observable 是 Rxjs 中最核心的物件。

我剛開始看Observable的實現方式時,旁邊放著 觀察者模式 的 類別圖。

然後我看的很困惑。所以我不建議使用觀察者模式對應Rxjs實作。

截取重要的幾段程式碼:

export class Observable<T> implements Subscribable<T> {

  /** Internal implementation detail, do not use directly. */
  public _isScalar: boolean = false;

  /** @deprecated This is an internal implementation detail, do not use. */
  source: Observable<any> | undefined;

  /** @deprecated This is an internal implementation detail, do not use. */
  operator: Operator<any, T> | undefined;

  /**
   * @constructor
   * @param {Function} subscribe the function that is called when the Observable is
   * initially subscribed to. This function is given a Subscriber, to which new values
   * can be `next`ed, or an `error` method can be called to raise an error, or
   * `complete` can be called to notify of a successful completion.
   */
  constructor(subscribe?: (this: Observable<T>, subscriber: Subscriber<T>) => TeardownLogic) {
    if (subscribe) {
      this._subscribe = subscribe;
    }
  }

  // .... 中間略

  subscribe(observer?: PartialObserver<T>): Subscription;
  /** @deprecated Use an observer instead of a complete callback */
  subscribe(next: null | undefined, error: null | undefined, complete: () => void): Subscription;
  /** @deprecated Use an observer instead of an error callback */
  subscribe(next: null | undefined, error: (error: any) => void, complete?: () => void): Subscription;
  /** @deprecated Use an observer instead of a complete callback */
  subscribe(next: (value: T) => void, error: null | undefined, complete: () => void): Subscription;
  subscribe(next?: (value: T) => void, error?: (error: any) => void, complete?: () => void): Subscription;

  subscribe(observerOrNext?: PartialObserver<T> | ((value: T) => void) | null,
            error?: ((error: any) => void) | null,
            complete?: (() => void) | null): Subscription {

    const { operator } = this;
    const sink = toSubscriber(observerOrNext, error, complete);

    if (operator) {
      sink.add(operator.call(sink, this.source));
    } else {
      sink.add(
        this.source || (config.useDeprecatedSynchronousErrorHandling && !sink.syncErrorThrowable) ?
        this._subscribe(sink) :
        this._trySubscribe(sink)
      );
    }

    if (config.useDeprecatedSynchronousErrorHandling) {
      if (sink.syncErrorThrowable) {
        sink.syncErrorThrowable = false;
        if (sink.syncErrorThrown) {
          throw sink.syncErrorValue;
        }
      }
    }

    return sink;
  }

  // ... (略)
}
  • code 3. Observable 程式碼。

不論 pipeOperator 的話,整個 Observable的重點,其實就是 subscribe函式。


Ts 筆記

code 3 中,建構子使用了 function type 的寫法(第19行),定義了 傳入的 function 的參數及 return 的型態。 另外,function type的第一個參數可以放 this,它可以限制 functionthis 型態。 (因此,此函式不能使用 arrow function

底線是一個常用的命名規則(21行,_subscribe屬性),代表變數或屬性是內部屬性。

subscribe 使用了 ts 函式多載的功能(27行 ~ 36行)。ts支援函式多載, 唯一的限制是,參數的個數必須要相同。 因此可以注意到,subscibe的所有參數都是 optional parameter

java 等語言不一樣。 ts 底層其實還是js。因此與其說它是函式多載,不如說它可以定義多個函式簽名。 在 copmile階段,編譯器會選擇最適合的函式簽名來檢查。 refer to

@deprecated 是軟體中很常使用的標籤(38行),代表被棄用的function。不論是公開的 Api 或者是 內部的 Api 都非常的好用。


看到 Subscribe 的第一一個參數,它可以是一個 PartialObserver<T> 或者是一個 function

看一下 PartialObserver的定義:

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

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

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

export type PartialObserver<T> = NextObserver<T> | ErrorObserver<T> | CompletionObserver<T>;
  • code 4. Observer interface

其實,PartialObserver 就是 Observer介面的別名,只是三個function中,你至少要定義其中一個。

Observer 及 Subscriber

Observer 其實是 Rxjs定義的界面。

定義界面的好處之一,是可以規範程式的 input 的規格。

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

code 5、Observer

另外,Subscriber 是個物件,它實現了 Observer 、並且繼承了 Subscription。現在只要知道,Subscriber 多了 取消訂閱的能力就好 (unsubscribe() 方法)。


我們繼續看 code 3

code 3第19行~23行。它的建構子其實很單純,就是把傳入的Subscriber function 放入一個內部的 _subscribe屬性。這部份的使用案例可以對應 code 2 看。

接著看到 subscribe() 函式。

code 3,41行。首先會透過 toSubscriber() 這個 utils 將 傳入的 Observer轉變成 Subscriber 。

前面提到,在 Rxjs的內部實現中,會把所有的 Observer界面,轉化為 Subscriber 物件,提供訂閱及取消訂閱的能力。

utils 放全域的 function,也是軟體中很常見的專案架構(code 3,41行)。

code 3,43~51行。接著,如果你是透過 creation operator 產生的 observablesubscribe()函式會呼叫這個 Operator(對應 code 1

如果不是的話,會試著去執行 _subscribe (對應 code2)。

code 3,53~60行。最下面的 if、else,控制 throw exception 。因為新版建議使用 Observer界面,取代將 subscribe 包在 try...catch中,因此這裡會檢查lib的 config檔的flag有沒有被打開;

如果有的話,再檢查是否有遇到例外需要throw回主函式的情型。


結論

Observablesubscribe() 中,可以了解到,Obsrevablesubscribe() 要發出的值有兩個來源:要馬是你 new 一個 Observable,並且把 subscribe、及 unsubscribe 的邏輯傳入;要馬是使用 Operator 來發出值。

使用情境來看,後者應該是更常見的情境。

另外,內部會將Observer轉成 Subscriber,是非常重要的一步。有了它,它有辦法實作 pipe

觀察者模式,通常是異步的,也不會有 ObservableOperator等物件。因此我開頭才說,最好不要用 觀察者模式 對應了解Rxjs的實作,否則只會越看越糊塗。

以目前看到的行為來說,Observable 更接近 疊代器模式



Written by Howard Chang , software engineer, programming lover, from Taiwan