陌小路的个人博客 陌小路的个人博客
首页
  • 技术专区

    • 面试
    • Vue
    • Electron
    • TypeScript
    • Serverless
    • GraphQL
  • 我的秋招之旅
  • 2019年终总结
Todo
收藏夹
关于作者
GitHub

陌小路

前端切图仔
首页
  • 技术专区

    • 面试
    • Vue
    • Electron
    • TypeScript
    • Serverless
    • GraphQL
  • 我的秋招之旅
  • 2019年终总结
Todo
收藏夹
关于作者
GitHub
  • Vue

  • React

  • 面试

  • Electron

  • Serverless

  • GraphQL

  • TypeScript

  • RxJS

    • 介绍
      • 概念
      • 背景
        • 应用场景?
        • 如何落地?
        • 上手难易程度如何?
        • 为什么需要它?它解决了什么问题?
        • 回调函数时代(callback)
        • Promise时代
        • Generator函数
        • async / await
        • RxJS
    • 前置知识点
    • Observable
    • Observer
    • Subscription与Subject
    • Cold-Observables与Hot-Observables
    • Schedulers
    • Operators概念
    • 创建型Operators
    • 转换操作符
    • 过滤操作符
    • 组合操作符
    • 总结
  • 工程化

  • Webpack

  • Nestjs

  • WebRTC & P2P

  • Docker

  • Linux

  • Git

  • Svelte

  • 踩坑日记 & 小Tips

  • 其他

  • technology
  • RxJS
陌小路
2020-12-18

介绍

# 概念

RxJS 是 Reactive Extensions for JavaScript 的缩写,起源于 Reactive Extensions,是一个基于可观测数据流 Stream 结合观察者模式和迭代器模式的一种异步编程的应用库。RxJS 是 Reactive Extensions 在 JavaScript 上的实现。

注意!它跟React没啥关系,笔者最初眼花把它看成了React.js的缩写(耻辱啊!!!)

对于陌生的技术而言,我们一般的思路莫过于,打开百度(google),搜索,然后查看官方文档,或者从零散的博客当中,去找寻能够理解这项技术的信息。但在很多时候,仅从一些只言片语中,的确也很难真正了解到一门技术的来龙去脉。

本文将从学习的角度来解析这项技术具备的价值以及能给我们现有项目中带来的好处。

# 背景

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

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

针对以上问题,我们可以由浅入深的来刨析一下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带来的价值。

异步事件处理方式

# 回调函数时代(callback)

使用场景:

  • 事件回调
  • Ajax请求
  • Node API
  • setTimeout、setInterval等异步事件回调

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

  • forEach
  • map
  • filter
[1, 2, 3].forEach(function (item, index) {
    console.log(item, index);
})
1
2
3

他们的使用方式只需要我们传入一个回调函数即可完成对一组数据的批量处理,很方便也很清晰明了。

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

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

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

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

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

# Promise时代

Promise最初是由社区提出(毕竟作为每天与奇奇怪怪的业务代码打交道的我们来说,一直用回调顶不住了啊),后来官方正式在ES6中将其加入语言标准,并进行了统一规范,让我们能够原生就能new一个Promise。

就优势而言,Promise带来了与回调函数不一样的编码方式,它采用链式调用,将数据一层一层往后抛,并且能够进行统一的异常捕获,不像使用回调函数就直接炸了,还得在众多的代码中一个个try catch。

话不多说,看码!


function readData(filePath) {
    return new Promise((resolve, reject) => {
        fs.readFile(filePath, 'utf-8', (err, data) => {
            if (err) reject(err);
            resolve(data);
        })
    });
}

readData('a.txt').then(res => {
    return readData('b.txt');
}).then(res => {
    return readData('c.txt');
}).then(res => {
    return readData('d.txt');
}).catch(err => {
    console.log(err);
})

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

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

优点:

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

缺点:

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

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

# Generator函数

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

  • function关键字与函数名之间有一个星号 "*" (推荐紧挨着function关键字)
  • 函数体内使用 yield· 表达式,定义不同的内部状态 (可以有多个yield`)
  • 直接调用 Generator函数并不会执行,也不会返回运行结果,而是返回一个遍历器对象(Iterator Object)
  • 依次调用遍历器对象的next方法,遍历 Generator函数内部的每一个状态
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 }
1
2
3
4
5
6
7
8
9
10
11
12

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

# async / await

相信在经过许多面试题的洗礼后,大家或多或少应该也知道这玩意其实就是一个语法糖,内部就是把Generator函数与自动执行器co进行了结合,让我们能以同步的方式编写异步代码,十分畅快。

有一说一,这玩意着实好用,要不是要考虑兼容性,真就想大面积使用这种方式。

再来看看用它编写的代码有多快乐:


async readFileData() {
    const data = await Promise.all([
        '异步事件一',
        '异步事件二',
        '异步事件三'
    ]);
    console.log(data);
}

1
2
3
4
5
6
7
8
9
10

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

# RxJS

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

优势:

  • 代码量的大幅度减少
  • 代码可读性的提高
  • 很好的处理异步
  • 事件管理、调度引擎
  • 十分丰富的操作符
  • 声明式的编程风格
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));

1
2
3
4
5
6
7
8
9
10
11
12
13

这里展示的仅仅是RxJS能表达能量的冰山一角,对于这种场景的处理办法还有多种方式。RxJS 擅长处理异步数据流,而且具有丰富的库函数。对于RxJS而言,他能将任意的Dom事件,或者是Promise转换成observables。

编辑
上次更新: 2023/11/25, 4:11:00
typescript 内置类型实现
前置知识点

← typescript 内置类型实现 前置知识点→

最近更新
01
github加速
01-01
02
在线小工具
01-01
03
Lora-Embeddings
11-27
更多文章>
Theme by Vdoing | Copyright © 2020-2024 STDSuperman | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式