小章魚觀察日記

RxJS

Jonny Huang

jonnyhuang@outlook.com

偽基百科:三眼章魚

特徵一

有三隻眼睛

  • next

  • error

  • complete

特徵二

很喜歡觀賞裙內風光,總是把頭鑽到別人的褲襠裡。

特徵三

不喜歡別人把頭探進自己裙子裡,發起脾氣的時候她就會踹下面的眼睛,主動警告它

因為它喜歡用眼睛觀察別人,所以我們叫它Observer(觀察者)

 

有穿裙子可以被別人看的,我們叫它

Observable (可觀察對象)

當然 Observable 也具有 Observer 的特性

Observer & Observable

大部分最後一個是 Observer,因為我們只需要觀察最終結果。

end

// Create simple observable that emits three values
const myObservable = Observable.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);

map

生氣時機:上面踹它的時候

        作法:踹下面同一隻眼睛,順帶加點料

map

var x

x = 1

x = 2

x = 3

var y

y = 10

y = 20

y = 30

complet

complet

彈珠圖

var x

x = 1

x = 2

x = 3

var y

y = 10

y = 20

y = 30

complet

complet

彈珠圖

filter

生氣時機:踐踏了它的原則

        作法:踹下面同一隻眼睛

filter

single

生氣時機:不小心只能有一次

        作法:踹下面紅眼睛(error)

single

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

}

export interface Subscribable<T> {
  subscribe(observerOrNext?: PartialObserver<T> | ((value: T) => void),
            error?: (error: any) => void,
            complete?: () => void): Unsubscribable;
}

Observable 關心的重點是連結(subscribe、unsubscribable)

Observable

Observer

Observable

export function map<T, R>(project: (value: T, index: number) => R, thisArg?: any): OperatorFunction<T, R> {
  return function mapOperation(source: Observable<T>): Observable<R> {
    if (typeof project !== 'function') {
      throw new TypeError('argument is not a function. Are you looking for `mapTo()`?');
    }
    return source.lift(new MapOperator(project, thisArg));
  };
}

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

  lift<R>(operator: Operator<T, R>): Observable<R> {
    const observable = new Observable<R>();
    observable.source = this;
    observable.operator = operator;
    return observable;
  }

}

Observable

        source:observable

destination:MapOperator

Observable 連接方式比較像俄羅斯套娃

export class MapOperator<T, R> implements Operator<T, R> {
  constructor(private project: (value: T, index: number) => R, private thisArg: any) {
  }

  call(subscriber: Subscriber<R>, source: any): any {
    return source.subscribe(new MapSubscriber(subscriber, this.project, this.thisArg));
  }
}

Operator 其實只負責 訂閱(subscribe)

Subscriber

Subscriber

Observable

Observable

class MapSubscriber<T, R> extends Subscriber<T> {

}

export class Subscriber<T> extends Subscription implements Observer<T> {

}

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

Subscriber 其實也是 Observer,Observer 只是 interface

Observer

next()

error()
complete()
 

Observer

next()

error()
complete()
 

export class Subscriber<T> extends Subscription implements Observer<T> {

 protected isStopped: boolean = false;

  next(value?: T): void {
    if (!this.isStopped) {
      this._next(value);
    }
  }

  error(err?: any): void {
    if (!this.isStopped) {
      this.isStopped = true;
      this._error(err);
    }
  }

  complete(): void {
    if (!this.isStopped) {
      this.isStopped = true;
      this._complete();
    }
  }

  unsubscribe(): void {
    if (this.closed) {
      return;
    }
    this.isStopped = true;
    super.unsubscribe();
  }

}

Subscriber 可以說是 Observer 介面的實作。

 

一但執行過 error() 或是 complete(),isStopped 會被設定成 true,

next() 就不會再運作。

protected _next(value: T): void {
    this.destination.next(value);
  }

  protected _error(err: any): void {
    this.destination.error(err);
    this.unsubscribe();
  }

  protected _complete(): void {
    this.destination.complete();
    this.unsubscribe();
  }
class MapSubscriber<T, R> extends Subscriber<T> {
  count: number = 0;
  private thisArg: any;

  constructor(destination: Subscriber<R>,
              private project: (value: T, index: number) => R,
              thisArg: any) {
    super(destination);
    this.thisArg = thisArg || this;
  }

  protected _next(value: T) {
    let result: any;
    try {
      result = this.project.call(this.thisArg, value, this.count++);
    } catch (err) {
      this.destination.error(err);
      return;
    }
    this.destination.next(result);
  }
}

其實我們也可以自己實作 RxJS 的 Operator

繼承 Subscriber 並依需求複寫

_next()_error()_complete()

class SingleSubscriber<T> extends Subscriber<T> {
  private seenValue: boolean = false;
  private singleValue: T;
  private index: number = 0;

  private applySingleValue(value: T): void {
    if (this.seenValue) {
      this.destination.error('Sequence contains more than one element');
    } else {
      this.seenValue = true;
      this.singleValue = value;
    }
  }

  protected _next(value: T): void {
    const index = this.index++;

    if (this.predicate) {
      this.tryNext(value, index);
    } else {
      this.applySingleValue(value);
    }
  }

}

next() 並不一定會呼叫後面的 next()

Single 在第2次呼叫 next()

會改呼叫下一個 Observer 的 error()

做事的只有 Subscriber

  • 過來時要做什麼?
  • 錯誤來時要做什麼?
  • 通知完成時要做什麼?
  • 自己做完事情之後是否要通知後面的 Subscriber?以及通知什麼?
class RetrySubscriber<T> extends Subscriber<T> {
  constructor(destination: Subscriber<any>,
              private count: number,
              private source: Observable<T>) {
    super(destination);
  }
  error(err: any) {
    if (!this.isStopped) {
      const { source, count } = this;
      if (count === 0) {
        return super.error(err);
      } else if (count > -1) {
        this.count = count - 1;
      }
      source.subscribe(this._unsubscribeAndRecycle());
    }
  }
}

retry 是複寫 error

retry :為什麼發生錯誤(error)後還可以繼續通知?

先捨棄掉再重新訂閱

class RepeatSubscriber<T> extends Subscriber<T> {
  constructor(destination: Subscriber<any>,
              private count: number,
              private source: Observable<T>) {
    super(destination);
  }
  complete() {
    if (!this.isStopped) {
      const { source, count } = this;
      if (count === 0) {
        return super.complete();
      } else if (count > -1) {
        this.count = count - 1;
      }
      source.subscribe(this._unsubscribeAndRecycle());
    }
  }
}

retry 是複寫 complete

repeat 呢?

先捨棄掉再重新訂閱

Q:Observable 重複訂閱會發生什麼事情?

export class Subject<T> extends Observable<T> implements SubscriptionLike {

observers: Observer<T>[] = [];
next(value?: T) {
    if (!this.isStopped) {
     ...
      for (let i = 0; i < len; i++) {
        copy[i].next(value);
      }
    }
  }

  error(err: any) {
    ...
    for (let i = 0; i < len; i++) {
      copy[i].error(err);
    }
    this.observers.length = 0;
  }

  complete() {
     ...
    for (let i = 0; i < len; i++) {
      copy[i].complete();
    }
    this.observers.length = 0;
  }
}

Q:Subject 如何多播給多個觀察者?

Subject 與 Observable 另一個差異就是它對外提供 next()error()complete() 三個方法,讓我們可以事後在變更值。

換個角度

認真你就輸了

用 SQL 邏輯來思考

  1. FROM

  2. JOIN

  3. WHERE

  4. SELECT

  5. DISTINCT

  6. ORDER BY

  7. TOP

資料:create、from、of

篩選:filter

合併:merge、concat

轉換:map

過濾:distinct

排序:

過濾:first、last、skip、take

rxjs_2018_06_11

By Jonny Huang

rxjs_2018_06_11

  • 1,974