浅谈 JavaScript 异步编程(四):JS 异步编程的另一种思路

什么是响应式编程

在 JavaScript 还在被 Callback Hell 的阴影笼罩时,来自 ReactiveX 组织的仁人志士,为 JavaScript 世界带来了被广泛使用的响应式编程。响应式编程和我们熟悉的命令式编程有很大的不同。它和声明式编程很想象,但强调使用可观察对象构建异步数据流。下面让我们通过一个例子来体会一下它们之间的区别:

拉取体系与推送体系

在继续介绍 RxJS 之前,我们有必要弄清楚所谓拉取体系(Pull System)和推送体系(Push System)间的区别。其中,后者是响应式编程的根基。

“拉取和推送是两种不同的协议,用来描述数据生产者 (Producer)如何与数据消费者 (Consumer)进行通信的”,RxJS 官方文档如此描述。在拉取中,消费者占主动地位,而生产者占被动地位,消费者自行决定什么时候接受数据,而生产者并不知道何时发出数据。在推送中,以上关系发生颠倒,由生产者决定何时发出数据,而消费者只能被动接受。

拉取体系与推送体系

在 JavaScript 中,拉取体系的典型例子是 Iterator,其中的消费者就是 iterator.next()。推送体系的典例是 Promise,其中的消费者是 promise.then()

利用 Generator 实现的求斐波那契数列的函数

从上图的代码中可以看到,只要 iterator.next() 不被调用,也就是消费者不去拉取数据,那么 fib() 内的代码就会被挂起。因此,在拉取体系中,我们主动向生产者索要数据。

我们回想一下先前的 fetch().then()fetch() 是生产者,then() 中的函数是消费者。此时并不存在我们拉取数据的说法,fetch() 自动启动,我们能做的只能被动接受它产生的数据,而且时机是不确定的。故在推送体系中,我们只能被动接受。

Observable 与异步数据流

由之前的叙述我们知道,JavaScript 本身存在着应用响应式编程的空间。然而,其中存在一道缺口:对于推送体系中的产生多个值的数据源,JavaScript 本身并没有很好的支持。先前提到的 Async Iteration 用途场景较为受限,很难完全填补这里的空白。至此,我们总算要开始介绍 RxJS 的核心概念——Observable 与异步数据流

RxJS 为了填补这道缺口,提出了 Observable 对象,代表能够产生若干个值的异步数据源。同时,采用观察者模式,让数据消费者能够通过订阅数据源来被动消费数据。

异步数据流则强调 Observable 产生的数据仿佛溪流一般,消费者既能改变流向,又能在溪流的任意位置获取数据。

下图是一个简单的例子,它以按钮被点击的事件作为 Observable,我们的消费者就是第二行中订阅函数的参数。注意这个 Observable 是一个典型的异步数据源:它可能产生若干个值,并且存在一定时间跨度。我们可以定义自己的 Observable,目前 RxJS 原生支持将 DOM Event、setInterval、Promise 等转换为异步数据源。

为了增强异步数据流给编写异步代码的舒适性,新版本的 RxJS 引入了 Piped Operators,使得我们可以顺着异步数据流的“溪流”,在数据流淌的过程中对数据做出修改,并让修改后的“溪流”最终流向我们的目标(消费者)。例如,使用 map 可以利用纯函数来修改输入的数据,并让修改后的数据继续沿着“溪流”流向下一个 Operator 或消费者。

实例

这是笔者参与维护的学院的课程平台。平台上的这个页面显示着从服务器获取的未结束的课程,并且支持使用搜索框筛选显示的课程。我们不妨想想,要如何使用 RxJS 实现这个页面的功能呢?

显然,卡片容器是数据消费者,它需要接受一个表示课程信息的数组。而从服务器获取的所有未结束的课程列表,以及搜索框的内容会是我们的两个数据源。因此我们的目标就是整合这两个数据源,并让搜索框能够发挥它的作用。下面来看看实际的代码以及概念图:

P1、P2、F1、F2 是几个不同的 Operator,用于对数据流做特殊的修改,这里暂不探究启具体作用。

可以发现,我们的这些代码几乎就像一篇操作指南一样,告诉了大家我们对数据流做了什么,这就是响应式编程的美妙之处;并且,异步数据流的概念也很符合我们对数据处理的思考,先干这个再干哪个,中间没有任何东西打断我们的思路。

小结

RxJS 的世界远远比先前的介绍更加复杂,但也更加有趣,这里由于篇幅原因只能介绍这些。总而言之,RxJS 代表的响应式编程是人们对 JavaScript 异步编程的一项伟大的补充,它解决了多数据异步数据流的处理问题,并且带来了许多理念先进且使用便捷的 Operators。

链接