前言
RxJS 的 Observable 有點難理解,其實 RxJS 相關的概念都有點難理解。畢竟 RxJS 引入了響應式程式設計這種新的模式,會不習慣是正常的。不過總得去理解嘛,而認識新的事物時,如果能夠參照一個合適的已知事物比對著,會比較容易理解吧。對於 Observable,類比 JS 中的函式,還是比較好的。
開始
封裝
先來看一個普通函式呼叫的例子:
1 2 3 4 5 6 7 |
function foo() { console.log('process...') } foo() // 輸出: // process... |
很簡單,函式 foo()
封裝了一段邏輯(這裡只是向控制檯輸出),然後通過呼叫函式,函式執行內部的邏輯。
再來看 RxJS Observable 的一個例子:
1 2 3 4 5 6 7 |
var foo = Rx.Observable.create(() => { console.log('process...') }) foo.subscribe() // 輸出: // process... |
上例中,通過 Rx.Observable.create()
來建立 Observable 物件,將同樣將一段程式碼邏輯封裝到 Observable 物件 foo
中,然後通過 foo.subscribe()
來執行封裝的程式碼邏輯。
對於普通函式和 Observable 物件,封裝的程式碼邏輯在每次呼叫時都會重新執行一次。從這一點來看,Observable 能夠和普通函式一樣實現封裝程式碼進行復用。
返回值
函式呼叫後可以有返回值:
1 2 3 4 5 6 7 8 9 |
function foo() { console.log('process...') return 42 } console.log(foo()) // 輸出: // process... // 42 |
Observable 執行後也會產生值,不過和函式直接返回的方式不同,要通過回撥函式方式獲取:
1 2 3 4 5 6 7 8 9 |
var foo = Rx.Observable.create((observer) => { console.log('process...') observer.next(42) }) foo.subscribe(value => console.log(value)) // 輸出: // process... // 42 |
Observable 物件內部是通過 observer.next(42)
這種方式返回值,而呼叫方則通過回撥函式來接收返回的資料。形式上比普通函式直接返回值囉嗦一些。
從呼叫方的角度來看,兩個過程分別是:
- 普通函式:呼叫 > 執行邏輯 > 返回資料
- Observable:訂閱(subscribe) > 執行邏輯 > 返回資料
從獲取返回值方式來看,呼叫函式是一種直接獲取資料的模式,從函式那裡“拿”(pull)資料;而 Observable 訂閱後,是要由 Observable 通過間接呼叫回撥函式的方式,將資料“推”(push)給呼叫方。
這裡 pull 和 push 的重要區別在於,push 模式下,Observable 可以決定什麼時候返回值,以及返回幾個值(即呼叫回撥函式的次數)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
var foo = Rx.Observable.create((observer) => { console.log('process...') observer.next(1) setTimeout(() => observer.next(2), 1000) }) console.log('before') foo.subscribe(value => console.log(value)) console.log('after') // 輸出: // before // process... // 1 // after // 2 |
上面例子中,Observable 返回了兩個值,第1個值同步返回,第2個值則是過了1秒後非同步返回。
也就是說,從返回值來說,Observable 相比普通函式區別在於:
- 可以返回多個值
- 可以非同步返回值
異常處理
函式執行可能出現異常情況,例如:
1 2 3 4 |
function foo() { console.log('process...') throw new Error('BUG!') } |
我們可以捕獲到異常狀態進行處理:
1 2 3 4 5 |
try { foo() } catch(e) { console.log('error: ' + e) } |
對於 Observable,也有錯誤處理的機制:
1 2 3 4 5 6 7 8 9 |
var foo = Rx.Observable.create((observer) => { console.log('process...') observer.error(new Error('BUG!')) }) foo.subscribe( value => console.log(value), e => console.log('error: ' + e) ) |
Observable 的 subscribe() 方法支援傳入額外的回撥函式,用於處理異常情況。和函式執行類似,出現錯誤之後,Observable 就不再繼續返回資料了。
subscribe() 方法還支援另一種形式傳入回撥函式:
1 2 3 4 |
foo.subscribe({ next(value) { console.log(value) }, error(e) { console.log('error: ' + e) } }) |
而這種形式下,傳入的物件和 Observable 內部執行函式中的 observer 引數在形式上就比較一致了。
中止執行
Observable 內部的邏輯可以非同步多個返回值,甚至返回無數個值:
1 2 3 4 5 6 7 8 9 10 11 |
var foo = Rx.Observable.create((observer) => { let i = 0 setInterval(() => observer.next(i++), 1000) }) foo.subscribe(i => console.log(i)) // 輸出: // 0 // 1 // 2 // ... |
上面例子中,Observable 物件每隔 1 秒會返回一個值給呼叫方。即使呼叫方不再需要資料,仍舊會繼續通過回撥函式向呼叫推送資料。
RxJS 提供了中止 Observable 執行的機制:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
var foo = Rx.Observable.create((observer) => { console.log('start') let i = 0 let timer = setInterval(() => observer.next(i++), 1000) return () => { clearInterval(timer) console.log('end') } }) var subscription = foo.subscribe(i => console.log(i)) setTimeout(() => subscription.unsubscribe(), 2500) // 輸出: // start // 0 // 1 // 2 // end |
subscribe() 方法返回一個訂閱物件(subscription),該物件上的 unsubscribe() 方法用於取消訂閱,也就是中止 Observable 內部邏輯的執行,停止返回新的資料。
對於具體的 Observable 物件是如何中止執行,則要由 Observable 在執行後返回一個用於中止執行的函式,像上面例子中的這種方式。
Observable 執行結束後,會觸發觀察者的 complete 回撥,所以可以這樣:
1 2 3 4 |
foo.subscribe({ next(value) { console.log(value) }, complete() { console.log('completed') } }) |
Observable 的觀察者共有上面三種回撥:
- next:獲得資料
- error:處理異常
- complete:執行結束
其中 next 可以被多次呼叫,error 和 complete 最多隻有一個被呼叫一次(任意一個被呼叫後不再觸發其他回撥)。
資料轉換
對於函式返回值,有時候我們要轉換後再使用,例如:
1 2 3 4 5 6 7 |
function foo() { return 1 } console.log(f00() * 2) // 輸出: // 2 |
對於 Observable 返回的值,也會有類似的情況,不過通常採用下面的方式:
1 2 3 4 5 6 7 8 9 10 11 |
var foo = Rx.Observable.create((observer) => { let i = 0 setInterval(() => observer.next(i++), 1000) }) foo.map(i => i * 2).subscribe(i => console.log(i)) // 輸出: // 0 // 2 // 4 // ... |
其實 foo.map() 返回了新的 Observable 物件,上面程式碼等價於:
1 2 |
var foo2 = foo.map(i => i * 2) foo2.subscribe(i => console.log(i)) |
Observable 物件 foo2 被訂閱時執行的內部邏輯可以簡單視為:
1 2 3 4 5 6 |
function subscribe(observer) { let mapFn = v => v * 2 foo.subscribe(v => { observer.next(mapFn(v)) }) } |
將這種對資料的處理和陣列進行比較看看:
1 2 |
var array = [0, 1, 2, 3, 4, 5] array.map(i => i * 2).forEach(i => console.log(i)) |
是不是有點像?
除了 map() 方法,Observable 還提供了多種轉換方法,如 filter() 用於過濾資料,find() 值返回第一個滿足條件的資料,reduce() 對資料進行累積處理,在執行結束後返回最終的資料。這些方法和陣列方法功能是類似的,只不過是對非同步返回的資料進行處理。還有一些轉換方法更加強大,例如可以 debounceTime() 可以在時間維度上對資料進行攔截等等。
Observable 的轉換方法,本質不過是建立了一個新的 Observable,新的 Observable 基於一定的邏輯對原 Observable 的返回值進行轉換處理,然後再推送給觀察者。
總結
Observable 就是一個奇怪的函式,它有和函式類似的東西,例如封裝了一段邏輯,每次呼叫時都會重新執行邏輯,執行有返回資料等;也有更特殊的特性,例如資料是推送(push)的方式返回給呼叫方法,返回值可以是非同步,可以返回多個值等。
不過將 Observable 視作特殊函式,至少對於理解 Observable 上是比較有幫助的。
Observable 也被視為 data stream(資料流),這是從 Observable 可以返回多個值的角度來看的,而資料轉換則是基於當前資料流建立新的資料流,例如:
不過上圖看到的只是資料,而將 Observable 視為特殊函式時,不應該忘了其內部邏輯,不然資料是怎麼產生的呢。
打賞支援我寫出更多好文章,謝謝!
打賞作者
打賞支援我寫出更多好文章,謝謝!
任選一種支付方式