从开发者角度来看,对于任何一项技术而言,我们经常会去谈论的,莫过于以下几点:

  • 应用场景?
  • 如何落地?
  • 上手难易程度如何?
  • 为什么需要它?它解决了什么问题?

针对以上问题,我们可以由浅入深的来刨析一下 RxJS 的相关理念。

应用场景?

假设我们有这样一个需求:
我们上传一个大文件之后,需要实时监听他的进度,并且待进度进行到 100 的时候停止监听。
对于一般的做法我们可以采用短轮询的方式来实现,在对于异步请求的封装的时候,如果我们采用 Promise 的方式,那么我们一般的做法就可以采用编写一个用于轮询的方法,获取返回值进行处理,如果进度没有完成则延迟一定时间再次调用该方法,同时在出现错误的时候需要捕获错误并处理。

显然,这样的处理方式无疑在一定程度上给开发者带来了一定开发和维护成本,因为这个过程更像是我们在观察一个事件,这个事件会多次触发并让我感知到,不仅如此还要具备取消订阅的能力,Promise 在处理这种事情时的方式其实并不友好,而 RxJS 对于异步数据流的管理就更加符合这种范式。
引用尤大的话:

我个人倾向于在适合 Rx 的地方用 Rx,但是不强求 Rx for everything。比较合适的例子就是比如多个服务端实时消息流,通过 Rx 进行高阶处理,最后到 view 层就是很清晰的一个 Observable,但是 view 层本身处理用户事件依然可以沿用现有的范式。

如何落地?

针对现有项目来说,如何与实际结合并保证原有项目的稳定性也的确是我们应该优先考虑的问题,毕竟任何一项技术如果无法落地实践,那么必然给我们带来的收益是比较有限的。

这里如果你是一名使用 Angular 的开发者,或许你应该知道 Angular 中深度集成了 Rxjs,只要你使用 Angular 框架,你就不可避免的会接触到 RxJs 相关的知识。

在一些需要对事件进行更为精确控制的场景下,比如我们想要监听点击事件 (click event),但点击三次之后不再监听。
那么这个时候引入 RxJS 进行功能开发是十分便利而有效的,让我们能省去对事件的监听并且记录点击的状态,以及需要处理取消监听的一些逻辑上的心理负担。
你也可以选择为你的大型项目引入 RxJS 进行数据流的统一管理规范,当然也不要给本不适合 RxJS 理念的场景强加使用,这样实际带来的效果可能并不明显。

上手难易程度如何?

如果你是一名具备一定开发经验的 JavaScript 开发者,那么几分钟或许你就能将 RxJS 应用到一些简单的实践中了。

为什么需要它?它解决了什么问题?

如果你是一名使用 JavaScript 的开发者,在面对众多的事件处理,以及复杂的数据解析转化时,是否常常容易写出十分低效的代码或者是臃肿的判断以及大量脏逻辑语句?

不仅如此,在 JavaScript 的世界里,就众多处理异步事件的场景中来看,“麻烦” 两个字似乎经常容易被提起,我们可以先从 JS 的异步事件的处理方式发展史中来细细品味 RxJS 带来的价值。

背景 - 图1

异步事件处理方式

回调函数时代(callback)

使用场景:

  • 事件回调
  • Ajax请求
  • Node API
  • setTimeoutsetInterval等异步事件回调

在上述场景中,我们最开始的处理方式就是在函数调用时传入一个回调函数,在同步或者异步事件完成之后,执行该回调函数。可以说在大部分简单场景下,采用回调函数的写法无疑是很方便的,比如我们熟知的几个高阶函数:

  • forEach
  • map
  • filter
    1. [1, 2, 3].forEach(function (item, index) {
    2. console.log(item, index);
    3. })
    他们的使用方式只需要我们传入一个回调函数即可完成对一组数据的批量处理,很方便也很清晰明了。

但在一些复杂业务的处理中,我们如果仍然秉持不抛弃不放弃的想法顽强的使用回调函数的方式就可能会出现下面的情况:

  1. fs.readFile('a.txt', 'utf-8', function(err, data) {
  2. fs.readFile('b.txt', 'utf-8', function(err, data1) {
  3. fs.readFile('c.txt', 'utf-8', function(err, data2) {
  4. // ......
  5. })
  6. })
  7. })

当然作为编写者来说,你可能觉得说这个很清晰啊,没啥不好的。但是如果再复杂点呢,如果调用的函数都不一样呢,如果每一个回调里面的内容都十分复杂呢。短期内自己可能清楚为什么这么写,目的是什么,但是过了一个月、三个月、一年后,你确定在众多业务代码中你还能找回当初的本心吗?

你会不会迫不及待的查找提交记录,这是哪个憨批写的,跟 shit…,卧槽怎么是我写的。

这时候,面对众多开发者苦不堪言的回调地域,终于还是有人出来造福人类了…

Promise 时代

Promise 最初是由社区提出(毕竟作为每天与奇奇怪怪的业务代码打交道的我们来说,一直用回调顶不住了啊),后来官方正式在 ES6 中将其加入语言标准,并进行了统一规范,让我们能够原生就能 new 一个 Promise。
就优势而言,Promise 带来了与回调函数不一样的编码方式,它采用链式调用,将数据一层一层往后抛,并且能够进行统一的异常捕获,不像使用回调函数就直接炸了,还得在众多的代码中一个个 try catch。
话不多说,看码!

  1. function readData(filePath) {
  2. return new Promise((resolve, reject) => {
  3. fs.readFile(filePath, 'utf-8', (err, data) => {
  4. if (err) reject(err);
  5. resolve(data);
  6. })
  7. });}
  8. readData('a.txt').then(res => {
  9. return readData('b.txt');
  10. }).then(res => {
  11. return readData('c.txt');
  12. }).then(res => {
  13. return readData('d.txt');
  14. }).catch(err => {
  15. console.log(err);
  16. })

对比一下,这种写法会不会就更加符合我们正常的思维逻辑了,这种顺序下,让人看上去十分舒畅,也更利于代码的维护。
优点:

  • 状态改变就不会再变,任何时候都能得到相同的结果
  • 将异步事件的处理流程化,写法更方便

缺点:

  • 无法取消
  • 错误无法被try catch(但是可以使用.catch方式)
  • 当处于pending状态时无法得知现在处在什么阶段

虽然 Promise 的出现在一定程度上提高了我们处理异步事件的效率,但是在需要与一些同步事件的进行混合处理时往往我们还需要面临一些并不太友好的代码迁移,我们需要把原本放置在外层的代码移到 Promise 的内部才能保证某异步事件完成之后再进行继续执行。

Generator 函数

ES6 新引入了 Generator 函数,可以通过 yield 关键字,把函数的执行流挂起,为改变执行流程提供了可能,从而为异步编程提供解决方案。形式上也是一个普通函数,但有几个显著的特征:

  • function关键字与函数名之间有一个星号 “*” (推荐紧挨着function关键字)
  • 函数体内使用 yield 表达式,定义不同的内部状态 (可以有多个yield
  • 直接调用 Generator函数并不会执行,也不会返回运行结果,而是返回一个遍历器对象(Iterator Object
  • 依次调用遍历器对象的next方法,遍历 Generator函数内部的每一个状态
    ```javascript function* read(){ let a= yield ‘666’; console.log(a); let b = yield ‘ass’; console.log(b); return 2}let it = read();

console.log(it.next()); // { value:’666’,done:false } console.log(it.next()); // { value:’ass’,done:false } console.log(it.next()); // { value:2,done:true } console.log(it.next()); // { value: undefined, done: true }

这种模式的写法我们可以自由的控制函数的执行机制,在需要的时候再让函数执行,但是对于日常项目中来说,这种写法也是不够友好的,无法给与使用者最直观的感受。

<a name="2b117771"></a>
### async / await
相信在经过许多面试题的洗礼后,大家或多或少应该也知道这玩意其实就是一个语法糖,内部就是把 Generator 函数与自动执行器 co 进行了结合,让我们能以同步的方式编写异步代码,十分畅快。
> 有一说一,这玩意着实好用,要不是要考虑兼容性,真就想大面积使用这种方式。

再来看看用它编写的代码有多快乐:
```javascript
async readFileData() {   
  const data = await Promise.all([       
    '异步事件一',   
    '异步事件二',      
    '异步事件三'   
  ]);  
  console.log(data);
}

直接把它当作同步方式来写,完全不要考虑把一堆代码复制粘贴的一个其他异步函数内部,属实简洁明了。

RxJS

它在使用方式上,跟 Promise 有点像,但在能力上比 Promise 强大多了,不仅仅能够以流的形式对数据进行控制,还内置许许多多的内置工具方法让我们能十分方便的处理各种数据层面的操作,让我们的代码如丝一般顺滑。
优势:

  • 代码量的大幅度减少
  • 代码可读性的提高
  • 很好的处理异步
  • 事件管理、调度引擎
  • 十分丰富的操作符
  • 声明式的编程风格
    ```javascript function readData(filePath) {
    return new Observable((observer) => {
    fs.readFile(filePath, ‘utf-8’, (err, data) => {
    if (err) observer.error(err);          
    observer.next(data); 
    
    })
    }); }

Rx.Observable.forkJoin( readData(‘a.txt’), readData(‘b.txt’), readData(‘c.txt’)) .subscribe(data => console.log(data)); ``` 这里展示的仅仅是 RxJS 能表达能量的冰山一角,对于这种场景的处理办法还有多种方式。RxJS 擅长处理异步数据流,而且具有丰富的库函数。对于 RxJS 而言,他能将任意的 DOM 事件,或者是 Promise 转换成 observables。