02-解锁反应式编程:初识RxJS

158 阅读7分钟

RxJS就是针对异步处理流程的lodash。通过提供的数十种操作符,RxJS提供了一套完整且强大的异步流程编排解决方案。通过RxJS让开发人员可以以一套统一的API管理和编排各种异步的事件、网络请求以及动画等。

现代的Web应用包含大量的非同步逻辑,其中,最常见的就是网络数据请求,包括AJAX/Fetch等在内的各种浏览器网络请求接口提供了异步响应的机制。对于开发人员来说,这也就意味着发送请求和接收响应的代码块之间的执行顺序是非线性的,我们需要借助于event callback,Promise,亦或是新的语法糖async/await来编排请求和响应体结构。对于单个网络请求而言,这往往不是太困难的事情,但真实的应用却更加复杂。

今天的Web应用包含了除简单网络请求之外的大量其他异步场景,例如定时执行的代码块(setTimeout/setInterval)、基于WebSocket的双向消息推送,基于JavaScript的插帧动画(requestAnimationFrame)、基于WebWorker的多线程等等,越来越复杂的应用场景和运行时环境为Web应用添加了难以预测的异步数据来源,当这些场景和数据交织在一起,如何让各种异步事件的发生能够被准确的预测,并通过合理的业务流程对这些异步事件进行编排变得重要。

一些常见的异步API

  • DOM Events
  • XMLHttpRequest
  • Fetch
  • WebSockets
  • Server Send Events
  • Service Worker
  • Node Stream
  • Timer

事实上这不是传统的前端应用开发者熟悉的领域,但是对于程序的世界却并不是一个新鲜的话题。在网络与通信行业,网络工程师每天都在面对这样的场景。如果你对于Unix网络编程有所了解,你也会觉得似曾相识。

在异步的世界里,比逻辑复杂度更让人挠头的是潜在的逻辑陷阱,这包括:

  • 竞态条件(Race Condition)
  • 内存泄漏(Memory Leak)
  • 复杂状态(Complex State)
  • 异常处理(Exception Handling)

Race Condition


竞态条件是源自计算机操作系统中的一种常见的逻辑陷阱,主要来自于多个进程/线程对于同一资源的多次异步访问,有可能会造成潜在的竞态条件问题。问题的核心是不同的使用者对于资源产生了并发的读/写请求,从而让使用者获取的资源情况/值会由于执行顺序的不同而不同。在计算机操作系统或是其他原生支持多线程的高级编程语言中,通常采用锁、原子对象、不可变对象等语言级特性来避免竞态条件产生。

Memory Leak


JavaScript作为一门原生支持GC特性的语言,通常我们应用开发者对于内存泄漏的感知并没有像C++这一类开发人员那般如履薄冰。然而,这并不意味着我们可以随心所欲的编写业务层代码。基于JavaScript开发的Web应用通常不会产生类似C++那样的指针溢出异常,我们所说的内存泄漏常常是指由于开发人员的疏忽,没有及时释放不需要使用的引用,从而让GC也无法回收这部分内存,随着时间的推移,将会产生越来越多的内存分配,直到超出浏览器宿主对于单个网页Tab的内存限制,而这在现代的SPA(Single-Page-App)类型的Web应用中将尤为重要,这是由于这一类通常强调局部刷新,应用持续在同一Tab中运行,细微的内存泄漏将会造成严重的性能问题。

Complex State


复杂的Web应用常常采用类似状态机的机制来驱动页面的运行,而复杂的Web应用通常具有更加复杂的状态。这也是现代的前端技术栈总是需要搭配合适的状态管理Lib来对整个应用进行状态数据管理的原因。复杂的交互和大量的异步触发会让应用状态的变化变得更加难以追踪和预测。

Exception Handling


JavaScript在语言层面仅提供了try/catch机制用来对代码的进行异常捕获,但这仅仅支持同步代码块,针对诸如callback、Promise.then等异步回调环节的异常无能为力。然而,大量的异步代码并不能总是确保分支逻辑正确,开发人员需要在异步数据产生错误和异常时进行额外的恢复现场和rollback操作,这带来了整整一倍的逻辑分支,让人更加抓狂。

通过以上的描述,我们或多或少可以意识到针对JavaScript的大量异步管理并不是一件容易的事,我们需要一个完整的解决方案。

RxJS 基础知识


RxJS的核心是一套基于Observable的封装,以及针对多个Observable序列进行组合来表达异步过程以及事件处理的类库。

有时候也可以将RxJS称之为Functional Reactive Programming,更准确的讲,是讲Functional Programming和Reactive Programming两种编程思想的结合。

严谨的说,RxJS确实借鉴了Functional Programming和Reactive Programming两种思想,但是是否能称之为Functional Reactive Programming(FRP)有不少的争议,在Rx官方的介绍中,也并不愿意直接将FRP与Rx画等号。

FRP通常表示处理随着时间连续改变的值,而Rx显然操作的是随时间的进程而产生的一系列离散的值,这是他们根本的不同,不过我们不用陷入这些学术性质的争论,物尽其用。

关于Reactive Extension(Rx)


前面多次提到了关于Rx,严格来说,Rx的全称是Reactive Extension,是一种与语言无关的响应式编程扩展API。Rx最早可以追溯到微软开发的Linq,也叫做Language-integrated Query,是为了解决C#中针对多种集合类型操作的不一致性问题,随后由社区的工程师持续的贡献,加入了对异步操作的支持。随着更多的科技公司例如Netflix、Trello、Github、Airbnb等陆续加入其中,微软牵头成立了一个开源小组,专门处理围绕Rx的开源提案,成为迄今为止最成功的开源项目提案之一。

Functional Reactive Programming


既然提到了FRP,我们有必要简单的了解一下。Functional Reactive Programming(FRP)是一种编程范式(Programming Paradigm),也可以认为是一种程序代码的组织形式。以我们熟知的OOP为例,通过对象的形式来组织代码逻辑,将属性和方法封装到同一个对象类型中,而在大多数支持OOP的编程语言中,我们是基于class的机制来实现对象的。与之不同的是,FRP强调以函数来组织应用的逻辑,将函数作为一等公民,以函数的视角来重新思考和理解整个应用的行为逻辑。FRP不是一种新的编程范式,而是融合了Functional Programming和Reactive Programming两种固有的编程思想。

Functional Programming

顾名思义,Functional Programming就是以function的视角来思考、设计和编写应用程序代码。

Reactive Programming

这里我们需要注意的是,Reactive Programming是一种思想,RxJS是遵循这一思想而实现的产物,我们不能盲目的将Rx等同于Reactive Programming。其他典型的包括Vue,同样遵循了Reactive Programming的思想来设计和实现。

Rx 并不等于Reactive Programming。

有兴趣的读者可以从我的另外一篇文章详细了解响应式编程的细节。

响应式基础 (1) :point_left:

以上便是我们需要了解的关于RxJS的一些预备知识,接下来我们将更深入的了解函数式编程,这也是理解RxJS的基础。

如果你觉得本文对你有些许启发,请持续关注我的公众号“戈伊星球”吧!