昨天我們介紹了 Subject 是什麼,今天要講 Subject 一些應用方式,以及 Subject 的另外三種變形。

Subject

昨天我們講到了 Subject 實際上就是 Observer Pattern 的實作,他會在內部管理一份 observer 的清單,並在接收到值時遍歷這份清單並送出值,所以我們可以這樣用 Subject

  1. var subject = new Rx.Subject();
  2. var observerA = {
  3. next: value => console.log('A next: ' + value),
  4. error: error => console.log('A error: ' + error),
  5. complete: () => console.log('A complete!')
  6. }
  7. var observerB = {
  8. next: value => console.log('B next: ' + value),
  9. error: error => console.log('B error: ' + error),
  10. complete: () => console.log('B complete!')
  11. }
  12. subject.subscribe(observerA);
  13. subject.subscribe(observerB);
  14. subject.next(1);
  15. // "A next: 1"
  16. // "B next: 1"
  17. subject.next(2);
  18. // "A next: 2"
  19. // "B next: 2"

這裡我們可以直接用 subject 的 next 方法傳送值,所有訂閱的 observer 就會接收到,又因為 Subject 本身是 Observable,所以這樣的使用方式很適合用在某些無法直接使用 Observable 的前端框架中,例如在 React 想對 DOM 的事件做監聽

  1. class MyButton extends React.Component {
  2. constructor(props) {
  3. super(props);
  4. this.state = { count: 0 };
  5. this.subject = new Rx.Subject();
  6. this.subject
  7. .mapTo(1)
  8. .scan((origin, next) => origin + next)
  9. .subscribe(x => {
  10. this.setState({ count: x })
  11. })
  12. }
  13. render() {
  14. return <button onClick={event => this.subject.next(event)}>{this.state.count}</button>
  15. }
  16. }

從上面的程式碼可以看出來,因為 React 本身 API 的關係,如果我們想要用 React 自訂的事件,我們沒辦法直接使用 Observable 的 creation operator 建立 observable,這時就可以靠 Subject 來做到這件事。
Subject 因為同時是 observer 和 observable,所以應用面很廣除了前面所提的之外,還有上一篇文章講的組播(multicase)特性也會在接下來的文章做更多應用的介紹,這裡先讓我們來看看 Subject 的三個變形。

BehaviorSubject

很多時候我們會希望 Subject 能代表當下的狀態,而不是單純的事件發送,也就是說如果今天有一個新的訂閱,我們希望 Subject 能立即給出最新的值,而不是沒有回應,例如下面這個例子

  1. var subject = new Rx.Subject();
  2. var observerA = {
  3. next: value => console.log('A next: ' + value),
  4. error: error => console.log('A error: ' + error),
  5. complete: () => console.log('A complete!')
  6. }
  7. var observerB = {
  8. next: value => console.log('B next: ' + value),
  9. error: error => console.log('B error: ' + error),
  10. complete: () => console.log('B complete!')
  11. }
  12. subject.subscribe(observerA);
  13. subject.next(1);
  14. // "A next: 1"
  15. subject.next(2);
  16. // "A next: 2"
  17. subject.next(3);
  18. // "A next: 3"
  19. setTimeout(() => {
  20. subject.subscribe(observerB); // 3 秒後才訂閱,observerB 不會收到任何值。
  21. },3000)

以上面這個例子來說,observerB 訂閱的之後,是不會有任何元素送給 observerB 的,因為在這之後沒有執行任何 subject.next(),但很多時候我們會希望 subject 能夠表達當前的狀態,在一訂閱時就能收到最新的狀態是什麼,而不是訂閱後要等到有變動才能接收到新的狀態,以這個例子來說,我們希望 observerB 訂閱時就能立即收到 3,希望做到這樣的效果就可以用 BehaviorSubject。
BehaviorSubject 跟 Subject 最大的不同就是 BehaviorSubject 是用來呈現當前的值,而不是單純的發送事件。BehaviorSubject 會記住最新一次發送的元素,並把該元素當作目前的值,在使用上 BehaviorSubject 建構式需要傳入一個參數來代表起始的狀態,範例如下

  1. var subject = new Rx.BehaviorSubject(0); // 0 為起始值
  2. var observerA = {
  3. next: value => console.log('A next: ' + value),
  4. error: error => console.log('A error: ' + error),
  5. complete: () => console.log('A complete!')
  6. }
  7. var observerB = {
  8. next: value => console.log('B next: ' + value),
  9. error: error => console.log('B error: ' + error),
  10. complete: () => console.log('B complete!')
  11. }
  12. subject.subscribe(observerA);
  13. // "A next: 0"
  14. subject.next(1);
  15. // "A next: 1"
  16. subject.next(2);
  17. // "A next: 2"
  18. subject.next(3);
  19. // "A next: 3"
  20. setTimeout(() => {
  21. subject.subscribe(observerB);
  22. // "B next: 3"
  23. },3000)

從上面這個範例可以看得出來 BehaviorSubject 在建立時就需要給定一個狀態,並在之後任何一次訂閱,就會先送出最新的狀態。其實這種行為就是一種狀態的表達而非單純的事件,就像是年齡跟生日一樣,年齡是一種狀態而生日就是事件;所以當我們想要用一個 stream 來表達年齡時,就應該用 BehaviorSubject。

ReplaySubject

在某些時候我們會希望 Subject 代表事件,但又能在新訂閱時重新發送最後的幾個元素,這時我們就可以用 ReplaySubject,範例如下

  1. var subject = new Rx.ReplaySubject(2); // 重複發送最後 2 個元素
  2. var observerA = {
  3. next: value => console.log('A next: ' + value),
  4. error: error => console.log('A error: ' + error),
  5. complete: () => console.log('A complete!')
  6. }
  7. var observerB = {
  8. next: value => console.log('B next: ' + value),
  9. error: error => console.log('B error: ' + error),
  10. complete: () => console.log('B complete!')
  11. }
  12. subject.subscribe(observerA);
  13. subject.next(1);
  14. // "A next: 1"
  15. subject.next(2);
  16. // "A next: 2"
  17. subject.next(3);
  18. // "A next: 3"
  19. setTimeout(() => {
  20. subject.subscribe(observerB);
  21. // "B next: 2"
  22. // "B next: 3"
  23. },3000)

可能會有人以為 ReplaySubject(1) 是不是就等同於 BehaviorSubject,其實是不一樣的,BehaviorSubject 在建立時就會有起始值,比如 BehaviorSubject(0) 起始值就是 0,BehaviorSubject 是代表著狀態而 ReplaySubject 只是事件的重放而已。

AsyncSubject

AsyncSubject 是最怪的一個變形,他有點像是 operator last,會在 subject 結束後送出最後一個值,範例如下

  1. var subject = new Rx.AsyncSubject();
  2. var observerA = {
  3. next: value => console.log('A next: ' + value),
  4. error: error => console.log('A error: ' + error),
  5. complete: () => console.log('A complete!')
  6. }
  7. var observerB = {
  8. next: value => console.log('B next: ' + value),
  9. error: error => console.log('B error: ' + error),
  10. complete: () => console.log('B complete!')
  11. }
  12. subject.subscribe(observerA);
  13. subject.next(1);
  14. subject.next(2);
  15. subject.next(3);
  16. subject.complete();
  17. // "A next: 3"
  18. // "A complete!"
  19. setTimeout(() => {
  20. subject.subscribe(observerB);
  21. // "B next: 3"
  22. // "B complete!"
  23. },3000)

從上面的程式碼可以看出來,AsyncSubject 會在 subject 結束後才送出最後一個值,其實這個行為跟 Promise 很像,都是等到事情結束後送出一個值,但實務上我們非常非常少用到 AsyncSubject,絕大部分的時候都是使用 BehaviorSubject 跟 ReplaySubject 或 Subject。
我們把 AsyncSubject 放在大腦的深處就好
~~

今日小結

今天介紹了 Subject 的一些應用方式,以及 BehaviorSubject, ReplaySubject, AsyncSubject 三個變形各自的特性介紹,不知道讀者麼是否有收穫呢?