RxJS速成 (下)

solenovex發表於2018-03-26

上一部分: http://www.cnblogs.com/cgzl/p/8641738.html

Subject

Subject比較特殊, 它即是Observable又是Observer.

作為Observable, Subject是比較特殊的, 它可以對多個Observer進行廣播, 而普通的Observable只能單播, 它有點像EventEmitters(事件發射器), 維護著多個註冊的Listeners.

作為Observable, 你可以去訂閱它, 提供一個Observer就會正常的收到推送的值. 從Observer的角度是無法分辨出這個Observable是單播的還是一個Subject.

從Subject內部來講, subscribe動作並沒有呼叫一個新的執行來傳遞值, 它只是把Observer註冊到一個列表裡, 就像其他庫的AddListener一樣.

作為Observer, 它是一個擁有next(), error(), complete()方法的物件, 呼叫next(value)就會為Subject提供一個新的值, 然後就會多播到註冊到這個Subject的Observers.

 

例子 subject.ts:

import { Subject } from "rxjs/Subject";

const subject = new Subject();

const subscriber1 = subject.subscribe({
    next: (v) => console.log(`observer1: ${v}`)
});
const subscriber2 = subject.subscribe({
    next: (v) => console.log(`observer2: ${v}`)
});

subject.next(1);
subscriber2.unsubscribe();
subject.next(2);

const subscriber3 = subject.subscribe({
    next: (v) => console.log(`observer3: ${v}`)
});

subject.next(3);

 

訂閱者1,2從開始就訂閱了subject. 然後subject推送值1的時候, 它們都收到了. 

然後訂閱者2, 取消了訂閱, 隨後subject推送值2, 只有訂閱者1收到了.

後來訂閱者3也訂閱了subject, 然後subject推送了3, 訂閱者1,3都收到了這個值.

 

下面是一個angular 5的例子:

app.component.html:

<h3>從Subject共享Observable到多個Subscribers</h3>
<input type="text" placeholder="start typing" (input)="mySubject.next($event)" (keyup)="mySubject.next($event)">

<br> Subscriber to input events got {{inputValue}}
<br>
<br> Subscriber to keyup events got {{keyValue}}

 

app.component.ts:

import { Component } from '@angular/core';
import { Subject } from 'rxjs/Subject';
import 'rxjs/add/operator/filter';
import 'rxjs/add/operator/map';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  title = 'app';

  keyValue: string;
  inputValue: string;

  mySubject: Subject<Event> = new Subject();

  constructor() {
    // subscriber 1
    this.mySubject.filter(({ type }) => type === 'keyup')
      .map(e => (<KeyboardEvent>e).key)
      .subscribe(value => this.keyValue = value);

    // subscriber 2
    this.mySubject.filter(({ type }) => type === 'input')
      .map(e => (<HTMLInputElement>e.target).value)
      .subscribe(value => this.inputValue = value);
  }
}

input和keyup動作都把event推送到mySubject, 然後mySubject把值推送給訂閱者, 訂閱者1通過過濾和對映它只處理keyup型別的事件, 而訂閱者2只處理input事件.

效果:

 

BehaviorSubject

BehaviorSubject 是Subject的一個變種, 它有一個當前值的概念, 它會把它上一次傳送給訂閱者值儲存起來, 一旦有新的Observer進行了訂閱, 那這個Observer馬上就會從BehaviorSubject收到這個當前值.

也可以這樣理解BehaviorSubject的特點:

  • 它代表一個隨時間變化的值, 例如, 生日的流就是Subject, 而一個人的年齡流就是BehaviorSubject.
  • 每個訂閱者都會從BehaviorSubject那裡得到它推送出來的初始值和最新的值.
  • 用例: 共享app狀態.

例子 behavior-subject.ts:

import { BehaviorSubject } from "rxjs/BehaviorSubject";

const subject = new BehaviorSubject(0);

subject.subscribe({
    next: v => console.log(`Observer1: ${v}`)
});

subject.next(1);
subject.next(2);

subject.subscribe({
    next: v => console.log(`Observer2: ${v}`)
});

subject.next(3);

 

效果:

 

常用Operators:

concat 

concat: 按順序合併observables. 只會在前一個observable結束之後才會訂閱下一個observable.

它適合用於順序處理, 例如http請求.

例子: 

import { Observable } from "rxjs/Observable";
import 'rxjs/add/observable/timer';
import 'rxjs/add/operator/mapTo';
import 'rxjs/add/observable/concat';

let firstReq = Observable.timer(3000).mapTo('First Response');
let secondReq = Observable.timer(1000).mapTo('Second Response');

Observable.concat(firstReq, secondReq)
    .subscribe(res => console.log(res));

 

效果:

 

merge

把多個輸入的observable交錯的混合成一個observable, 不按順序.

merge實際上是訂閱了每個輸入的observable, 它只是把輸入的observable的值不帶任何轉換的傳送給輸出的Observable. 只有當所有輸入的observable都結束了, 輸出的observable才會結束. 任何在輸入observable傳遞來的錯誤都會立即發射到輸出的observable, 也就是把整個流都殺死了 .

例子:

import { Observable } from "rxjs/Observable";
import 'rxjs/add/observable/timer';
import 'rxjs/add/operator/mapTo';
import 'rxjs/add/observable/merge';

let firstReq = Observable.timer(3000).mapTo('First Response');
let secondReq = Observable.timer(1000).mapTo('Second Response');

Observable.merge(firstReq, secondReq)
    .subscribe(res => console.log(res));

 

效果:

 

mergeMap (原來叫flatMap)

mergeMap把每個輸入的Observable的值對映成Observable, 然後把它們混合成一個Observable.

mergeMap可以把巢狀的observables拼合成非巢狀的observable.

它有這些好處:

  • 不必編寫巢狀的subscribe()
  • 把每個observable發出來的值轉換成另一個observable
  • 自動訂閱內部的observable並且把它們(可能)交錯的合成一排.

這個還是通過例子來理解比較好:

import { Observable } from "rxjs/Observable";
import 'rxjs/add/observable/from';
import 'rxjs/add/operator/mergeMap';

function getData() {
    const students = Observable.from([
        { name: 'Dave', age: 17 },
        { name: 'Nick', age: 18 },
        { name: 'Lee', age: 15 }
    ]);

    const teachers = Observable.from([
        { name: 'Miss Wan', age: 28 },
        { name: 'Mrs Wang', age: 31 },
    ]);

    return Observable.create(
        observer => {
            observer.next(students);
            observer.next(teachers);
        }
    );
}

getData()
    .mergeMap(persons => persons)
    .subscribe(
        p => console.log(`Subscriber got ${p.name} - ${p.age}`)
    );

 

效果:

 

switchMap

switchMap把每個值都對映成Observable, 然後使用switch把這些內部的Observables合併成一個.

switchMap有一部分很想mergeMap, 但也僅僅是一部分像而已.

因為它還具有取消的效果, 每次發射的時候, 前一個內部的observable會被取消, 下一個observable會被訂閱. 可以把這個理解為切換到一個新的observable上了.

 

這個還是看marble圖比較好理解:

 

例子: 

// 立即發出值, 然後每5秒發出值
const source = Rx.Observable.timer(0, 5000);
// 當 source 發出值時切換到新的內部 observable,發出新的內部 observable 所發出的值
const example = source.switchMap(() => Rx.Observable.interval(500));
// 輸出: 0,1,2,3,4,5,6,7,8,9...0,1,2,3,4,5,6,7,8
const subscribe = example.subscribe(val => console.log(val));

更好的例子是: 網速比較慢的時候, 客戶端傳送了多次重複的請求, 如果前一次請求在2秒內沒有返回的話, 那麼就取消前一次請求, 不再需要前一次請求的結果了, 這裡就應該使用debounceTime配合switchMap.

 

mergeMap vs switchMap的例子

mergeMap:

import { Observable } from "rxjs/Observable";
import 'rxjs/add/observable/interval';
import 'rxjs/add/operator/take';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/mergeMap';
import 'rxjs/add/operator/switchMap';

const outer = Observable.interval(1000).take(2);

const combined = outer.mergeMap(x => {
    return Observable.interval(400)
        .take(3)
        .map(y => `outer ${x}: inner ${y}`);
});

combined.subscribe(res => console.log(`result ${res}`));

 

效果:

switchMap:

import { Observable } from "rxjs/Observable";
import 'rxjs/add/observable/interval';
import 'rxjs/add/operator/take';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/mergeMap';
import 'rxjs/add/operator/switchMap';

const outer = Observable.interval(1000).take(2);

const combined = outer.switchMap(x => {
    return Observable.interval(400)
        .take(3)
        .map(y => `outer ${x}: inner ${y}`);
});

combined.subscribe(res => console.log(`result ${res}`));

 

 

zip

zip操作符也會合並多個輸入的observables成為一個observable. 多個輸入的observable的值, 按順序, 按索引進行合併, 如果某一個observable在該索引上的值還沒有發射值, 那麼會等它, 直到所有的輸入observables在該索引位置上的值都發射出來, 輸出的observable才會發射該索引的值.

例子:

import { Observable } from "rxjs/Observable";
import 'rxjs/add/observable/of';
import 'rxjs/add/observable/zip';

let age$ = Observable.of<number>(27, 25, 29);
let name$ = Observable.of<string>('Foo', 'Bar', 'Beer');
let isDev$ = Observable.of<boolean>(true, true, false);

Observable
    .zip(age$,
        name$,
        isDev$,
        (age: number, name: string, isDev: boolean) => ({ age, name, isDev }))
    .subscribe(x => console.log(x));

 

效果:

 

就不往下寫了, 其實看文件就行, 最重要的還是上一部分.