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
。