What is RxJS?
RxJS是ReactiveX程式設計理念的JavaScript版本。ReactiveX是一種針對非同步資料流的程式設計。簡單來說,它將一切資料,包括HTTP請求,DOM事件或者普通資料等包裝成流的形式,然後用強大豐富的操作符對流進行處理,使你能以同步程式設計的方式處理非同步資料,並組合不同的操作符來輕鬆優雅的實現你所需要的功能
下面廢話不說, 直接切入正題.
準備專案
我使用typescript來介紹rxjs. 因為我主要是在angular專案裡面用ts.
全域性安裝typescript:
npm install -g typescript
全域性安裝ts-node:
npm install -g ts-node
建立一個資料夾learn-rxjs, 進入並執行:
npm init
安裝rxjs:
npm install rxjs --save
RxJS的主要成員
- Observable: 一系列值的生產者
- Observer: 它是observable值的消費者
- Subscriber: 連線observer和observable
- Operator: 可以在資料流的途中對值進行轉換的操作符
- Subject: 既包括Observable也包括Observer
Observable, Observer, Subscriber的角色關係:
工廠生產雜誌, 郵遞員去送雜誌, 就相當於是Observable, 郵遞員給你帶來了啥? 帶來了雜誌, 然後(next)雜誌, next雜誌.....
把雜誌帶給了誰? 看看這對夫婦, 可能是丈夫來付賬單訂雜誌, 他就是Subscriber. 而這本女性雜誌肯定不是丈夫來看(如果他是正經丈夫的話), 而妻子沒有直接去訂閱雜誌, 但是她看這本雜誌有用(知道怎麼去用它).
所以可以這樣理解, 丈夫(Subscriber)把Observable和Observer聯絡到了一起, 就是Subscriber為Observable提供了一個Observer(丈夫訂雜誌, 告訴快遞員把貨給他媳婦就行).
Observable可以在Observer上呼叫三種方法(快遞員跟他妻子可能會有三種情況...好像這麼說不太恰當), 當Observable把資料(雜誌)傳遞過來的時候, 這三種情況是:
- next(), 這期雜誌送完了, 等待下一期吧
- error(), 送雜誌的時候出現問題了, 沒送到.
- complete(), 訂的雜誌都處理完了, 以後不送了.
下面這個圖講的就是從Observable訂閱訊息, 並且在Observer裡面處理它們:
Observable允許:
- 訂閱/取消訂閱它的資料流
- 傳送下一個值給Observer
- 告訴Observer發生了錯誤以及錯誤的資訊
- 告訴Observer整個流結束了.
Observer可以提供:
- 一個可以處理流(stream)上的next的值的function
- 處理錯誤的function
- 處理流結束的function
建立Observable
- Observable.from(), 把陣列或iterable物件轉換成Observable
- Observable.create(), 返回一個可以在Observer上呼叫方法的Observable.
- Observable.fromEvent(), 把event轉換成Observable.
- Observable.fromPromise(), 把Promise轉換成Observable.
- Observable.range(), 在指定範圍內返回一串數.
Observable.from()
observable_from.ts:
import { Observable } from "rxjs/Observable"; // 這裡沒有使用Rx物件而是直接使用其下面的Observable物件, 因為Rx裡面很多的功能都用不上. import 'rxjs/add/observable/from'; // 這裡我需要使用from 操縱符(operator) let persons = [ { name: 'Dave', age: 34, salary: 2000 }, { name: 'Nick', age: 37, salary: 32000 }, { name: 'Howie', age: 40, salary: 26000 }, { name: 'Brian', age: 40, salary: 30000 }, { name: 'Kevin', age: 47, salary: 24000 }, ]; let index = 1; Observable.from(persons) .subscribe( person => { console.log(index++, person); }, err => console.log(err), () => console.log("Streaming is over.") );
subscribe裡面有3個function, 這3個function就是Observer.
第一個function是指當前這個person到來的時候需要做什麼;
第二個是錯誤發生的時候做什麼;
第三個function就是流都走完的時候做什麼.
注意, 是當執行到.subscribe()的時候, Observable才開始推送資料.
執行這個例子需要執行下面的命令:
ts-node observable_from.ts
Observable.create()
Observable.create是Observable建構函式的一個別名而已. 它只有一個引數就是subscribe function.
observable_creates.ts:
import { Observable } from "rxjs/Observable"; function getData() { let persons = [ { name: 'Dave', age: 34, salary: 2000 }, { name: 'Nick', age: 37, salary: 32000 }, { name: 'Howie', age: 40, salary: 26000 }, { name: 'Brian', age: 40, salary: 30000 }, { name: 'Kevin', age: 47, salary: 24000 }, ]; return Observable.create( observer => { // 這部分就是subscribe function persons.forEach(p => observer.next(p)); observer.complete(); } ); } getData() .subscribe( person => console.log(person.name), err => console.error(err), () => console.log("Streaming is over.") );
create裡面的部分是subscribe function. 這部分可以理解為, 每當有人訂閱這個Observable的時候, Observable會為他提供一個Observer.
在這裡面, observer使用next方法對person進行推送. 當迴圈結束的時候, 使用complete()方法通知Observable流結束了.
儘管getDate裡面create了Observable, 但是整個資料流動並不是在這時就開始的. 在這個地方, 這只不過是個宣告而已.
只有當有人去訂閱這個Observable的時候, 整個資料流才會流動.
執行該檔案:
RxJS Operator(操作符)
Operator是一個function, 它有一個輸入, 還有一個輸出. 這個function輸入是Observable輸出也是Observable.
在function裡面, 可以做一些轉換的動作
下面是幾個例子:
observablePersons.filter(p => p.age > 40);
這個filter function和陣列的filter類似, 它接受另一個function(也可以叫predicate)作為引數, 這個function提供了某種標準, 通過這個標準可以判定是否當前的元素可以被送到訂閱者那裡.
p => p.age > 40
這個function, 是pure function, 在functional programming(函數語言程式設計)裡面, pure function是這樣定義的: 如果引數是一樣的, 無論外界環境怎麼變化, 它的結果肯定是一樣的.
pure function不與外界打交道, 不儲存到資料庫, 不會儲存檔案, 不依賴於時間....
而這個filter function呢, 在函數語言程式設計裡面是一個high order function.
什麼是High order function?
如果一個function的引數可以是另一個function, 或者它可以返回另一個function, 那麼它就是High Order function.
Marble 圖
首先記住這個網址: http://rxmarbles.com/
有時候您可以通過文件檢視operator的功能, 有時候文件不是很好理解, 這時你可以參考一下marble 圖.
例如 map:
可以看到map接受一個function作為引數, 通過該function可以把每個元素按照function的邏輯進行轉換.
例如 filter:
filter就是按條件過濾, 只讓合格的元素通過.
例 debounceTime (恢復時間):
如果該元素後10毫秒內, 沒有出現其它元素, 那麼該元素就可以通過.
例 reduce:
這個也和陣列的reduce是一個意思.
例子
import { Observable } from "rxjs/Observable"; import 'rxjs/add/observable/from'; import 'rxjs/add/operator/map'; import 'rxjs/add/operator/reduce'; let persons = [ { name: 'Dave', age: 34, salary: 2000 }, { name: 'Nick', age: 37, salary: 32000 }, { name: 'Howie', age: 40, salary: 26000 }, { name: 'Brian', age: 40, salary: 30000 }, { name: 'Kevin', age: 47, salary: 24000 }, ]; Observable.from(persons) .map(p => p.salary) .reduce((total, current) => total+ current, 0) .subscribe( totalSalary => console.log(`Total salary is ${totalSalary}`), err => console.log(err) );
這個例子非常的簡單, 典型的map-reduce, 就不講了.
結果如下:
用現實世界中鍊鋼生產流程的例子來解釋使用Operator來進行Reactive資料流處理的過程:
原料(礦石)整個過程中會經過很多個工作站, 這裡每個工作站都可以看作是RxJS的operator, 原料經過這些operator之後, 成品就被生產了出來.
每個工作站(operator)都是可以被組合使用的, 所以可以再加幾個工作站也行.
錯誤處理
Observable是會發生錯誤的, 如果錯誤被髮送到了Observer的話, 整個流就結束了.
但是做Reactive程式設計的話, 有一個原則: Reactive的程式應該很有彈性/韌性.
也就是說, 即使錯誤發生了, 程式也應該繼續執行.
但是如果error function在Observer被呼叫了的話, 那就太晚了, 這樣流就停止了.
那麼如何在error到達Observer之前對其進行攔截, 以便流可以繼續走下去或者說這個流停止了,然後另外一個流替它繼續走下去?
錯誤處理的Operators:
- error() 被Observable在Observer上呼叫
- catch() 在subscriber裡並且在oserver得到它(錯誤)之前攔截錯誤,
- retry(n) 立即重試最多n次
- retryWhen(fn) 按照引數function的預定邏輯進行重試
使用catch()進行錯誤處理:
observable_catch.ts:
import { Observable } from "rxjs/Observable"; import 'rxjs/add/observable/from'; import 'rxjs/add/operator/catch'; import 'rxjs/add/operator/map'; function getFromGoogle(): Observable<any> { return Observable.create(function subscribe(observer) { observer.next('https://google.com'); observer.error({ message: 'Google can\'t be reached.', status: 404, }); observer.complete(); }); } function getFromBing(): Observable<any> { return Observable.create(function subscribe(observer) { observer.next('https://global.bing.com'); observer.complete(); }); } function getFromBaidu(): Observable<any> { return Observable.create(function subscribe(observer) { observer.next('https://www.baidu.com'); observer.complete(); }); } getFromGoogle() .catch(err => { console.error(`Error: ${err.status}: ${err.message}`); if(err.status === 404) { return getFromBaidu(); } else { return getFromBing(); } }) .map(x => `The site is : ${x}`) .subscribe( x => console.log('Subscriber got: ' + x), err => console.error(err), () => console.log('The stream is over.') );
在subscribe之前呼叫catch, catch裡可以進行流的替換動作.
執行結果如下:
相當於:
Hot 和 Cold Observable
Cold: Observable可以為每個Subscriber建立新的資料生產者
Hot: 每個Subscriber從訂閱的時候開始在同一個資料生產者那裡共享其餘的資料.
從原理來說是這樣的: Cold內部會建立一個新的資料生產者, 而Hot則會一直使用外部的資料生產者.
舉個例子:
Cold: 就相當於我在騰訊視訊買體育視訊會員, 可以從頭看裡面的足球比賽.
Hot: 就相當於看足球比賽的現場直播, 如果來晚了, 那麼前面就看不到了.
Share Operator
share() 操作符允許多個訂閱者共享同一個Observable. 也就是把Cold變成Hot.
例子 observable_share.ts:
import { Observable } from "rxjs/Observable"; import 'rxjs/add/observable/interval'; import 'rxjs/add/operator/take'; import 'rxjs/add/operator/share'; const numbers = Observable .interval(1000) .take(5) .share(); function subscribeToNumbers(name) { numbers.subscribe( x => console.log(`${name}: ${x}`) ); } subscribeToNumbers('Dave'); const anotherSubscription = () => subscribeToNumbers('Nick'); setTimeout(anotherSubscription, 2500);
這裡interval是每隔1秒產生一個資料, take(5)表示取5個數, 也就是1,2,3,4,5.
然後share()就把這個observable從cold變成了hot的.
後邊Dave進行了訂閱.
2.5秒以後, Nick進行了訂閱.
最後結果是: