RxJS入門

美團點評點餐發表於2017-08-01

RxJS是什麼?

官方:RxJS 是 Reactive Extensions for JavaScript 的縮寫,起源於 Reactive Extensions,是一個基於可觀測資料流在非同步程式設計應用中的庫。RxJS 是 Reactive Extensions 在 JavaScript 上的實現。

一般說到RxJS,都會講他是基於流的響應式的結合觀察者和迭代器模式的一種庫。所以下面會從這幾個關鍵詞來講。


前置知識點

響應式程式設計(RP —— Reactive Programming)

響應式程式設計是一種面向資料流和變化傳播的程式設計正規化。在程式語言中很方便地表達靜態或動態的資料流,而相關的計算模型會自動將變化的值通過資料流進行傳播。—— wikipedia
  • 響應式程式設計是使用非同步資料流進行程式設計。常見的非同步資料流包含Event buses。用包含這些事件在內的任何東西建立資料流(Data stream),監聽他並作出響應。
  • 只關注業務邏輯互相依賴的事件而不是實現細節
  • 適用於大量和資料有關的事件互動,特別是高實時性要求

以點選按鈕這一個事件流舉例:

圖片

多次點選按鈕事件流舉例:

圖片

  • 一個流就是一個不間斷的按照時間排序的序列。它產生三種不同型別事件: 值,錯誤,完成的訊號。對這三個定義事件處理函式,就可以非同步的捕獲這些事件
  • 每個stream有多個方法,呼叫時會基於原來的流返回一個新的流,原來的流不做修改,保證不可變性
  • 資料流支援鏈式呼叫,你可以組合不同的函式來處理流,建立和過濾不同的流。甚至一個流或多個流可以作為另外一個流的輸入。你可以合併兩個資料流。你可以過濾一個資料流,從中獲取一個包含你感興趣的事件的資料流。你可以將來自一個資料流的值對映到另外一個資料流

觀察者模式(釋出-訂閱模式)

例子:購房者和售房部之間的資訊訂閱。購房者訂閱售房部的房價資訊,售房部維護一張需要資訊的客戶表,當有資訊時,遍歷表給符合條件的購房者推送釋出房屋資訊。這裡,購房者擔任觀察者的角色,售房部是被觀察的角色,當售房部資訊發生變化,則自動推送資訊給購房者。
圖片

結論:流(售房部/rx.js的Observable)是被觀察的,某個函式訂閱流的某個事件(推送房價),該函式是觀察者(購房者/rx.js的Observer)。當流的某個事件產生了,對應的函式就會被執行。

迭代器模式

提供一種方法順序訪問一個聚合物件中的各個元素,而不需要暴露該物件的內部表示。最常見的就是JavaScript 中像 Array、Set 等這些內建的可迭代型別,可以通過 iterator 方法來獲取一個迭代物件,呼叫迭代物件的 next 方法將獲取一個元素物件。
var iterable = [1, 2];
var iterator = iterable[Symbol.iterator]();
iterator.next(); // => { value: "1", done: false}
iterator.next(); // => { value: "2", done: false}
iterator.next(); // => { value: undefined, done: true}複製程式碼
由上可知JavaScript 的 Iterator 只有一個 next 方法,這個 next 方法只會回傳這兩種結果。iterator通過呼叫next獲取值,是一種pull資料的形式。

與Promise的區別

圖片

  1. Promise本質上也是一個Observable,能使用fromPromise把Promise轉成Observable
  2. 但是Promise .then()只能返回一個值,Observable可以返回多個值
  3. Promise要麼resolve要麼reject,並且只響應一次。而Observable可以響應多次
  4. Promise不能取消,Observable可以呼叫unsubscribe()取消訂閱

解決的問題

  • 同步和非同步的統一
  • 可組合的資料變更過程
  • 資料和檢視的精確繫結
  • 條件變更之後的自動重新計算


核心概念

概述關係

圖片

Observable —— 被觀察者

Rxjs是觀察者 + 迭代器模式的結合,Observable作為被觀察者,是一個值或事件的流集合。就像是一個序列,裡面的元素會隨著時間推送。
var observable = Rx.Observable
// 通過create方法建立一個Observable
// 回撥函式會接受observer引數,也就是觀察者角色
	.create(function(observer) {
		observer.next('hi');
		observer.next('world');

        setTimeout(() => {
			observer.next('這一段是非同步操作');
		}, 30)
	})

// 訂閱這個 observable
// 只有在訂閱之後,才會在流Observable變化的時候,呼叫observer提供的方法,並通知他	
// 訂閱之後也可以取消訂閱,呼叫unsubscribe()即可
console.log('start')
var subscription = observable.subscribe(function(value) {
	console.log(value);
})
console.log('end')
setTimeOut(()=> {
  subscription.unsubscribe()
}, 5000)


// 程式會依次輸出
'start'
"hi"
'world'
'end'
'這一段是非同步操作'複製程式碼
所以,Observable不同於觀察者模式中的被觀察者,他沒有一份需要維護訂閱者的清單,他只是一個函式。想要訂閱他只需要傳進回撥函式observer就好。並且,Observable 可以同時處理同步和非同步操作!
同時,有很多建立Observable的方法,常用的如下:
圖片


Operator —— 操作符

操作Observable的函式就是操作符。他會接受傳入的Observable,但是會返回新的Observable。用map舉例說明。
Rx.Observable.of(2)
             .map(v => v * 2) 
             .subscribe(v => console.log('output:' + v));
// output:4複製程式碼
下面介紹幾個常用的操作符。具體的API視覺化資料流動可參見寶石圖
圖片

Observer —— 觀察者

和迭代器模式一一對應,提供三個方法,next、error、complete
var Observer = {
    next(value) { /* 處理值*/ },
    error(error) { /* 處理異常 */ },
    complete() { /* 處理已完成態 */ }
};

next(): 接收Observable發出的值  (必傳)
error(): 不同於迭代器裡面用try catch,Observer用error方法接收錯誤 (可選)
complete(): 當沒有新的資料發出的時候,觸發操作  (可選)複製程式碼

Subject —— 觀察模式的實現,並繼承Observable

同一個Observable可以被多個observer訂閱。和addListener類似她們由Subject維護列表,Subject可以向多個Observer多路推送數值,是一類特殊的Observable。
  • 每一個Subject都是一個Observable,可以訂閱他。從Observer的視角看,它並不能區分自己的執行環境是普通Observable的單路推送還是基於Subject的多路推送
  • 每一個Subject也可以是Observer,因為他同樣由next、error、complete方法組成,呼叫next方法,Subject會給所有在他上面註冊的Observer多路推送當前的值
// 建立一個Observable,一秒鐘輸出一個數字,只取三個就結束
var source = Rx.Observable.interval(1000).take(3);

// 定義兩個observer物件
var observerA = {
    next: value => console.log('A next: ' + value),
    error: error => console.log('A error: ' + error),
    complete: () => console.log('A complete!')
}

var observerB = {
    next: value => console.log('B next: ' + value),
    error: error => console.log('B error: ' + error),
    complete: () => console.log('B complete!')
}

// 建立一個subject —— 特殊的Observable
var subject = new Rx.Subject()

// observerA訂閱Subject
subject.subscribe(observerA)

// Subject又以observer的身份訂閱Observable
source.subscribe(subject);

setTimeout(() => {
    subject.subscribe(observerB);
}, 1000);

// 輸出:
// "A next: 0"
// "A next: 1"
// "B next: 1"
// "A next: 2"
// "B next: 2"
// "A complete!"
// "B complete!"
 A、B兩個observer互不影響,是獨立的複製程式碼

Scheduler —— 控制Observable的時間節點

控制一個Observable的訂閱開始執行和值送達的時機。RxJS 5中提供了4種scheduler。一般operator會有預設的scheduler。
  • queue —— 遞迴的時候,queue會把遞迴阻塞,避免不必要的效能損耗
  • asap —— as soon as possible,表現為setTimeout時間為0。多用於永不退訂的Observable,比如輪詢
  • async —— 規定當前的Observable執行方式為非同步,用setInterval實現。
  • animationFrame —— Window.requestAnimationFrame這個API實現,適合高頻率的UI動畫觸發
var observable = Rx.Observable.create(function (observer) {
    observer.next(1);
    observer.next(2);
    observer.next(3);
    observer.complete();
});

console.log('before subscribe');
observable.observeOn(Rx.Scheduler.async) // 本來是同步的,變成了非同步
.subscribe({
    next: (value) => { console.log(value); },
    error: (err) => { console.log('Error: ' + err); },
    complete: () => { console.log('complete'); }
});
console.log('after subscribe');

// "before subscribe"
// "after subscribe"
// 1
// 2
// 3
// "complete"複製程式碼

應用場景

例子: Trello 、 Teambition等協作工作軟體
圖片

核心:檢視的狀態是一個時間軸上的若干流的疊加效果。
業務場景特點:
  1. 有很多資料,非常多關於資料的操作
  2. 展示的資料是多個資料組合而成,比如任務、對應owner、標籤等
  3. 同一個資料的更新,可能來自不同的發起方
  4. 新增的資料需要的資料處理規則應該和原來的相同

解決:
  1. 資料通過快取和非同步方式獲取
  2. 把每個資料流管道組合起來,流的疊合就是最後的資料
  3. 獲取和訂閱放在一起,也就不需要知道資料的來源是哪裡了
  4. 現在和未來的資料merge之後通過相同的API處理,保證資料的規則相同

其他


資料



相關文章