Reactive Programming 101
Orginal from : The introduction to Reactive Programming you’ve been missing
Reactive Programming 響應式編程
Reactive programming is programming with asynchronous data streams.
響應式編程是異步數據流
這不是什麼新概念,一個點擊事件都是一個異步事件流,你可以觀察並且做一些副作用。
數據流可以在任何地方,變量,用戶輸入,屬性,caches,數據結構。
在往上,你可以對流做一些更改,合流,截流,改道,篩選。combine, create and filter。
一個流是一些列隨時間正在進行的事件。會發出3種信號,值,錯誤,完成。
這些發出的事件被我們異步地捕捉,定義3個觸發函數分別對應3個信號(值,錯誤,完成)。返回對應的函數時觸發對應的參數。
有時候我們只需要注意返回信號的觸發函數,而不需要其他信號的觸發函數。
監聽流的行爲叫做訂購(subscribing)。監聽函數定義爲觀察者(observers).
流被觀察,叫做觀察者設計模式(Observer Design Pattern)。
舉例做一個記錄點擊數的按鈕,在大部分響應式庫裏,流通常綁定函數。
當觸發這些函數時,會返回一個新的流。流和新流之間互不干擾。
這裏的流是clickStream,新流是counterStream。
clickStream: ---c----c--c----c------c-->
vvvvv map(c becomes 1) vvvv
---1----1--1----1------1-->
vvvvvvvvv scan(+) vvvvvvvvv
counterStream: ---1----2--3----4------5-->
觸發map(f)函數(返回新流)時,會替換對應值,這裏替換c爲1。
scan(g)函數會相加之前的值在這個流上(map函數觸發的新流),
最終兩個函數觸發完以後,返回counterStream流。
爲了體現響應式編程的優點,創建一個雙擊事件流,這個流會把大於2次以上的點擊全部計算成雙擊。
原理如下
灰色區域就是對流的修改,也就是3個函數。
先對擊數做提取,假設以250mm爲界限,提取出對應的擊數。
然後把擊數替換成數值
最後做一個filter,把>2的篩選出來。
Example 實際案例
選用JS和RxJS作爲工具。
以Twitter推薦follow爲例,這個部分建議要follow的人。
重點分析:
- 在啓動時,從api中讀取用戶信息並顯示3個建議
- 刷新時,加載其他3個建議
- 點擊x關閉時,對應建議消失,並出現新的建議
- 每一行顯示3個建議的信息和其連接
在啓動時,從api中讀取用戶信息並顯示3個建議
簡化成 1)request 2)get responce 3)render responce
把請求想象成流,一開始是這樣
--a------|->
a 就是api請求 `'https://api.github.com/users'`
當一個請求事件發生時,有兩個信息,when and what,
何時該執行 就是請求發生時的時間,
what 就是請求內容,這裏是一串url。
在Rx*裏面,創建一個流的官方名字是觀察者,但是直接叫流(stream)更合適。
var requestStream = Rx.Observable.just('https://api.github.com/users');
訂閱流,以便操作,然後用Ajax回調函數,處理這個異步請求。
requestStream.subscribe(function(requestUrl) {
// execute the request
var responseStream = Rx.Observable.create(function (observer) {
jQuery.getJSON(requestUrl)
.done(function(response) { observer.onNext(response); })
.fail(function(jqXHR, status, error) { observer.onError(error); })
.always(function() { observer.onCompleted(); });
});
responseStream.subscribe(function(response) {
// do something with the response
});
}
Rx.Observable.create()
用於創建專屬定製流,通過把數據事件傳通告給各個觀察者,或者訂閱者。
Promise 也是一個觀察者,只不過接收一個參數。
Rx流卻可以處理很多。
map(f) 從流a提取數據,實施函數f,然後產生流b。會產生流中流,分流。
本例中,返回流如果用map,會產生新的流(流走了,收不到?)。同時請求和返回都是以promise單獨存在。
我們需要讓map不產生新的流,以便能夠接受。
所以用flatMap
,扁平版map,不會產生流b
requestStream: --a-----b--c------------|->
responseStream: -----A--------B-----C---|->
收到response流後,就可以渲染了。
刷新
做一個刷新點擊流(刷新按鈕),同時請求流需要依靠刷新點擊流。
RxJS內置對應工具
var refreshButton = document.querySelector('.refresh');
var refreshClickStream = Rx.Observable.fromEvent(refreshButton, 'click');
刷新事件流本身不帶有api url,需要給予對url通過map。
把請求流連接刷新流
var requestOnRefreshStream = refreshClickStream
.map(function() {
var randomOffset = Math.floor(Math.random()*500);
return 'https://api.github.com/users?since=' + randomOffset;
});
var startupRequestStream = Rx.Observable.just('https://api.github.com/users');
合併兩個流
stream A: ---a--------e-----o----->
stream B: -----B---C-----D-------->
vvvvvvvvv merge vvvvvvvvv
---a-B---C--e--D--o----->
modeling 3 suggestion
每一個建議當成流,
整體流程如下
refreshClickStream: ----------o--------o---->
requestStream: -r--------r--------r---->
responseStream: ----R---------R------R-->
suggestion1Stream: ----s-----N---s----N-s-->
suggestion2Stream: ----q-----N---q----N-q-->
suggestion3Stream: ----t-----N---t----N-t-->
n爲null
或者設置在啓動時數據默認爲n
refreshClickStream: ----------o---------o---->
requestStream: -r--------r---------r---->
responseStream: ----R----------R------R-->
suggestion1Stream: -N--s-----N----s----N-s-->
suggestion2Stream: -N--q-----N----q----N-q-->
suggestion3Stream: -N--t-----N----t----N-t-->
關閉 出現
關閉一個建議,加載一個新的。
requestStream: --r--------------->
responseStream: ------R----------->
close1ClickStream: ------------c----->
suggestion1Stream: ------s-----s----->
Rx*有函數combineLatest
可以綁定兩個流最近的 如圖:
stream A: --a-----------e--------i-------->
stream B: -----b----c--------d-------q---->
vvvvvvvv combineLatest(f) vvvvvvv
----AB---AC--EC---ED--ID--IQ---->
可以用來綁定close1ClickStream
和suggestion1Stream
。