概念
学习angular2以上版本,或多或少会接触到Observable、subscribe等东西,本来打着用会Rx的API就万事大吉了,但随着时间的推移,对响应式编程产生了一点兴趣,慢慢的把自己的过程式编程思维转变一下。
我看到的响应式编程最有用的两个概念:纯函数(Pure Function)、数据不可变(Immutability)。
数据不可变:面向对象的思维来说,数据被封在对象中,所有的修改的历史也被隐藏,但当对象继承时还是会出现数据可变情况;对于函数式编程来说,数据都是不能改变的,所以整个历史都是有迹可循。程序debug会更加容易。
纯函数:函数的输出只和输入参数有关系,程序的结果变得更可预见,减少Bug的出现。
Rx在前端解决了页面组件间共享状态问题,举个例子:
A状态被B、C组件影响或者依赖,那么就会遇到问题,A状态会被B/C组件修改。一旦这种共享状态多起来了,那么整个应用变得越来越不可预测:遇到状态异常,究竟是谁改变的?看了一下,D、E、F、G函数会使用这个状态,H、I、J、K组件会影响这个状态,debug起来就极其麻烦。这其实就是老生常谈的要避免全局变量的问题。但是前端基本满地都是共享状态,一个状态被多个组件依赖或者影响基本是不可避免的问题。我们要规避上述不可预测的问题,就只有规定谁都不可以修改状态。
怎么做到?
答:谁要修改数据,你就得新建数据(Subjects订阅中间件内容)。共享相同结构,新建不同内容。
在ng4构建的单页面应用项目,在还未使用Observable(可观察对象)这种概念的情况前,异步调用http接口用的是promise或者ng4封装好的http模块(返回是Observable),遇到复杂的非父子通信的跨组件通信,项目的解决方案是用eventproxy模块工具进行相应。虽然eventproxy用起来非常方便,但组件交互非常混乱,不可预测情况极多,debug困难。
理解Rx操作符的工作原理(集合到集合的过渡)
延迟计算
所有的 Observable 对象一定会等到订阅后,才开始执行,如果没有订阅就不会执行。
let source = Rx.Observable.from([1,2,3,4,5]);
let example = source.map(x => x + 1);
上面的示例中,因为 example 对象还未被订阅,所以不会进行运算。这跟数组不一样,具体如下:
let source = [1,2,3,4,5];
let example = source.map(x => x + 1);
以上代码运行后,example 中就包含已运算后的值。
渐进式取值
数组中的操作符如:filter、map 每次都会完整执行并返回一个新的数组,才会继续下一步运算。具体示例如下:
let source = [1,2,3,4,5];
let example = source.filter(x => x % 2 === 0) // [2, 4]
.map(x => x + 1) // [3, 5]
为了更好地理解数组操作符的运算过程,我们可以参考下图:
虽然 Observable 运算符每次都会返回一个新的 Observable 对象,但每个元素都是渐进式获取的,且每个元素都会经过操作符链的运算后才输出,而不会像数组那样,每个阶段都得完整运算。具体示例如下:
- source 发出 1,执行 filter 过滤操作,返回 false,该值被过滤掉
- source 发出 2,执行 filter 过滤操作,返回 true,该值被保留,接着执行 map 操作,值被处理成 3,最后通过 console.log 输出
- source 发出 3,执行 filter 过滤操作,返回 false,该值被过滤掉
- source 发出 4,执行 filter 过滤操作,返回 true,该值被保留,接着执行 map 操作,值被处理成 5,最后通过 console.log 输出
- source 发出 5,执行 filter 过滤操作,返回 false,该值被过滤掉
为了更好地理解 Observable 操作符的运算过程,我们可以参考下图:
思考题:一个get请求的接口接受一个id参数,但这个id参数是用一个数组装起来而且不知道里面有多少个id,现在有一个需求,是遍历这个id数组取出每一个id作为一次接口的参数,每请求完一次接口两秒后再请求下一个id参数的接口,这个要怎么实现啊? 现在只做到合并所有id并行请求接口,但这个两秒要怎么实现间隔啊?
答案在文末。这个思考有利于理解Observable的工作流程。
Observable vs Promise
相同点:都是异步操作。
不同点:
1、Promise仅提供一个异步操作解决方案,没有Observable如此多的操作符,工具符去实现响应式编程
2、Promise一旦调用,无法终止,一定会走到最后或报错,Observable随时可以终止
3、Observable随着时间的推移发出多个值,每次返回的都是一个Observable可实现多值发送
思考题答案:
let observable=Rx.Observable.from([1,2,3,4])
.concatMap(val=>Rx.Observable.fromPromise(getData(val)).delay(2000))
.subscribe(console.info)
getData(){
// ...
fetch(url, { method: 'get' }).then(res => res.json()); //返回promise, ng的http模块返回observable
}
此处正确的思路应该集体到个体的拆分,concatMap就是一个Observable的集合同时也是最外层Observable的个体,要把集合里面的走完才会走到订阅(subscribe),不要简单认为一个集合的个体会像上边动图数组的流程先delay再异步请求再delay。思考一定要思考。
错误答案:
let observable=Rx.Observable.from([1,2,3,4])
.delay(2000)
.concatMap(val=>Rx.Observable.fromPromise(getData(val)))
.subscribe(console.info)