你有一份 Rx 编程秘籍请签收

一、背景
在学习 Rx 编程的过程中,理解 Observable 这个概念至关重要,常规学习过程中,通常需要进行多次“碰壁”才能逐渐“开悟”。这个有点像小时候学骑自行车,必须摔几次才能掌握一样。当然如果有办法能“言传”,则可以少走一些弯路,尽快领悟 Rx 的精妙。
二、Observable
Observable 从字面翻译来说叫做“可观察者”,换言之就是某种“数据源”或者“事件源”,这种数据源具有可被观察的能力,这个和你主动去捞数据有本质区别。用一个形象的比喻就是 Observable 好比是水龙头,你可以去打开水龙头——订阅 Observable,然后水——数据就会源源不断流出。这就是响应式编程的核心思想——变主动为被动。不过这个不在本篇文章中详解。

(图片来源自网络)
Observable 是一种概念,可以通过不同的方式去具体实现,本文通过高阶函数来实现两个常用 Observable:fromEvent 和 Interval。通过讲解对 Observable 的订阅和取消订阅两个行为来帮助读者真正理解 Observable 是什么。
三、高阶函数
高阶函数的概念来源于函数式编程,简单的定义就是一个函数的入参或者返回值是一个函数的函数。例如:
ps:高阶函数能做的事情很多,这里仅仅针对本文需要的情形进行使用。
上面这个 foo 函数的调用并不会直接打印 hello world,而只是把这个 hello world 给缓存起来。后面我们根据实际需要调用返回出来的 bar 函数,然后真正去执行打印 hello world 的工作。
为啥要做这么一步封装呢?实际上这么做的效果就是“延迟”了调用。而一切的精髓就在这个“延迟”两个字里面。我们实际上是对一种行为进行了包装,看上去就像某种一致的东西,好比是快递盒子。

(图片来源自网络)
里面可以装不同的东西,但对于物流来说就是统一的东西。因此,就可以形成对快递盒的统一操作,比如堆叠、运输、存储、甚至是打开盒子这个动作也是一致的。
回到前面的例子,调用 foo 函数,相当于打包了一个快递盒,这个快递盒里面有一个固定的程序,就是当打开这个快递盒(调用 bar)时执行一个打印操作。
我们可以有 foo1、foo2、foo3……里面有各种各样的程序,但是这些 foos,都有一个共同的操作就是“打开”。(前提是这个 foo 会返回一个函数,这样才能满足“打开”的操作,即调用返回的函数)。
四、快递盒模型
4.1 快递盒模型 1:fromEvent
有了上面的基础,下面我们就来看一下 Rx 编程中最常用的一个 Observable—fromEvent(……)。对于 Rx 编程的初学者,起初很难理解 fromEvent(……)和 addEventListener(……)有什么区别。
如果直接执行这个代码,确实效果是一样的。那么区别在哪儿呢?最直接的区别是,subscribe 函数作用在 fromEvent(……)上而不是 btn 上,而 addEventListener 是直接作用在 btn 上的。subscribe 函数是某种“打开”操作,而 fromEvent(……)则是某种快递盒。
fromEvent 实际上是对 addEventListener 的“延迟”调用
哦!fromEvent 本质上是高阶函数
至于如何实现 subscribe 来完成“打开”操作,不在本文讨论范围,在 Rx 编程中,这个 subscribe 的动作叫做“订阅”。“订阅”就是所有 Observable 的统一具备的操作。再次强调:本文中对 Observable 的“调用”在逻辑上相当于 subscribe。
下面再举一个例子,基本可以让读者举二反 N 了。
4.2 快递盒模型 2:interval
Rx 中有一个 interval,它和 setInterval 有什么区别呢?
估计有人已经开始抢答了,interval 就是对 setInterval 的延迟调用!bingo!
从上面两个例子来看,无论是 fromEvent(……)还是 Interval(……),虽然内部是完全不同的逻辑,但是他们同属于“快递盒”这种东西,我们把它称之为 Observable——可观察者。
fromEvent 和 Interval 本身只是制作“快递盒”的模型,只有调用后返回的东西才是“快递盒”,即 fromEvent(btn,"click")、interval(1000) 等等...
五、高阶快递盒
有了上面的基础,下面开始进阶:我们拥有了那么多快递盒,那么就可以对这些快递盒再封装。

在文章开头说了,快递盒统一了一些操作,所以我们可以把许许多多的快递盒堆叠在一起,即组合成一个大的快递盒!这个大的快递盒和小的快递盒一样,具有“打开”操作(即订阅)。当我们打开这个大的快递盒的时候,会发生什么呢?
可以有很多种不同的可能性,比如可以逐个打开小的快递盒(concat),或者一次性打开所有小的快递盒(merge),也可以只打开那个最容易打开的快递盒(race)。
下面是一个简化版的 merge 方法:
我们还是拿之前的 fromEvent 和 interval 来举例吧!

使用 merge 方法对两个 Observable 进行组合:
当我们“打开”(订阅)这个大快递盒 ob 的时候,其中两个小快递盒也会被“打开”(订阅),任意一个小快递盒里面的逻辑都会被执行,我们就合并(merge)了两个 Observable,变成了一个。
这就是我们为什么要辛辛苦苦把各种异步函数封装成快递盒(Observable)的原因了——方便对他们进行统一操作!当然仅仅只是“打开”(订阅)这个操作只是最初级的功能,下面开始进阶。
六、销毁快递盒
6.1 销毁快递盒——取消订阅

我们还是以 fromEvent 为例子,之前我们写了一个简单的高阶函数,作为对 addEventListener 的封装:
当我们调用这个函数的时候,就生成了一个快递盒(fromEvent(btn,'click'))。当我们调用了这个函数返回的函数的时候,就是打开了快递盒(fromEvent(btn,'click')(console.log))。
那么我们怎么去销毁这个打开的快递盒呢?
首先我们需要得到一个已经打开的快递盒,上面的函数调用结果是 void,我们无法做任何操作,所以我们需要构造出一个打开状态的快递盒。还是使用高阶函数的思想:在返回的函数里面再返回一个函数,用于销毁操作。
同理,对于 interval,我们也可以如法炮制:
6.2 销毁高阶快递盒

我们以 merge 为例:
当我们销毁大快递盒的时候,就会把里面所有的小快递盒一起销毁。
六、补充
到这里我们已经将 Observable 的两个重要操作(订阅、取消订阅)讲完了,值得注意的是,取消订阅这个行为并非是作用于 Observable 上,而是作用于已经“打开”的快递盒(订阅 Observable 后返回的东西)之上!
Observable 除此以外,还有两个重要操作,即发出事件、完成/异常,(这两个操作属于是由 Observable 主动发起的回调,和操作的方向是相反的,所以其实不能称之为操作)。
这个两个行为用快递盒就不那么形象了,我们可以将 Observable 比做是水龙头,原先的打开快递盒变成拧开水龙头,而我们传入的回调函数就可以比喻成接水的水杯!由于大家对回调函数已经非常熟悉了,所以本文就不再赘述了。
七、后记
总结一下我们学习的内容,我们通过高阶函数将一些操作进行了“延迟”,并赋予了统一的行为,比如“订阅”就是延迟执行了异步函数,“取消订阅”就是在上面的基础上再“延迟”执行了销毁资源的函数。
这些所谓的“延迟”执行就是 Rx 编程中幕后最难理解,也是最核心的部分。Rx 的本质就是将异步函数封装起来,然后抽象成四大行为:订阅、取消订阅、发出事件、完成/异常。
实际实现 Rx 库的方法有很多,本文只是利用了高阶函数的思想来帮助大家理解 Observable 的本质,在官方实现的版本中,Observable 这个快递盒并非是高阶函数,而是一个对象,但本质上是一样的,这里引出了一个话题:函数式编程与面向对象的异同,请听下回分解。
作者:vivo 互联网开发团队-Li Yuxiang
版权声明: 本文为 InfoQ 作者【vivo互联网技术】的原创文章。
原文链接:【http://xie.infoq.cn/article/749c6e51488cc95a934816f6b】。文章转载请联系作者。
评论