上篇文章介绍了一些 RxJS 的相关概念,本文通过学习 Marble Test
进一步的理解 RxJS。
Marble Diagram 是理解 RxJS 的重要辅助工具,在 RxJS 的文档中有很多以时间为轴的图,那就是 Marble Diagram。而 Marble Test 就是测试某个 Observable 是否满足某个 Marble Diagram 的方法,能帮助我们更好地理解“时间”在 RxJS 中到底是起到了什么作用,也能够让我们更好地理解 Observable 的转换(如通过各种 operators)到底发生了什么。
本文涉及的代码均在 RxJS v6 版本,此版本官方提供了
rxjs/testing
,在此版本之前可能需要外部的测试库辅助,但基本概念是类似的。
Marble Diagram
Marble Diagram
是描述 Observable 状态重要的可视化工具,它提供了一种模式用来描述我们的 Observable 发生了什么:
- 处理中:准备数据的阶段,这些时刻不会和 Observer 有交流
- 发出数据:通知 Observer 的 next
- 完成:通知 Observer 的 complete
- 出错:通知 Observer 的 error
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5X2VCWFZ-1611228499572)(/img/posts/rxjs/rxjs-marble-diagram.png)]
Marble Diagram
上图中可以看到,横轴是时间,且流向是从左到右。圆形表示发出的数据,也就是 marble,所以才叫做 Marble Diagram
。可以很容易地想象 Observable 产生 marble 在时间轴上的分布应该是有规律可循的,比如:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BWGxC8Mt-1611228499574)(/img/posts/rxjs/timer.png)]
timer(3000, 1000)的 Marble Diagram
timer(3000, 1000)
的含义是指 3s 后开始发出第一个数据,然后每隔 1s 发出下一个数据,所以 0 之前的时间长度应该是任意两个数据之间时间长度的 3 倍。
那我们如何在测试中提现这种类似的时间关系呢?我们可以通过一种特定的语法来表示 Marble Diagram
中状态,这就是 Marble Test
的核心。
Marble Test 语法
-
连接符,表示时间片,在测试用例中可以将 1 个时间片等同为 1ms,所以----
时间长度为 4ms(如果出现的只有连接符,如-
或----
则表示一个不会发出任何值,也不会结束的 Observable,和 NEVER 一样)[a-z0-9]
小写英文字母或数字,表示发出数据,但并不表示发出数据的值一定是他们本身,Marble Test
支持提供额外的数据映射,而它们也会占用一个时间片,所以在没有提供额外的数据映射时--a-0-b-
表示 3ms 时发出 ‘a’,5ms 时发出 ‘0’(默认是字符串类型)[0-9]+[ms|s|m]
即数字加上时间单位,比如 9m 表示 9 分钟,等同于 9*60s,因为单位时间为 1ms,如果只使用上面连接符的方式在表示一些时间比较长的 Observable 比较繁琐。3ms a 1ms
和---a-
是等价的' '
空白字符,上面的例子中出现了空格,它是不占用时间片的,可以用于类似上面这种必须分隔开的场景(3msa1ms
的含义完全不同),也可以用于代码的可读性|
表示图中的 complete,即告知 Observer 的 complete 方法,-a--|
表示 2ms 时发出 ‘a’,5ms 时结束#
表示图中的 error,即告知 Observer 的 error 方法,-a-#
表示 2ms 时发出 ‘a’,4ms 时出现错误()
上面的图中,每个数据的发出、结束、出错看起来都是有时间间隔的,但现实肯定不总是这样,可能在同一个时间片中有多个数据的发出,但是不能写成类似-ab-
的形式,因为 ‘a’ 和 ‘b’ 占用了不同的时间片,想要表示在同一个时间片时就需要使用括号了,所以-(ab)-
表示 2ms 时同时发出了 ‘a’ 和 ‘b’^
表示 Observer 订阅的时刻,只对hot Observable
生效!
表示 Observer 退订的时刻,注意和|
的区别,前者是 Observer 不再对数据感兴趣了,后者是 Observable 已经发出了所有了数据
由上面的语法组成的字符串,比如
-(ab)-
叫做Marble 字符串
,可以看到利用这个字符串可以模拟出时间的流逝、数据的发出、完成、错误、订阅开始、订阅结束的这些状态。
关于 cold 和 hot
cold Observable 和 hot Observable 的差异性是学习 RxJS 绕不开的概念,而且经常和 Observable 的 lazy 特性搞混。无论是 cold 还是 hot,都具有 lazy 的特性,即都是在 Observer 进行了 subscribe 操作时才对 Observer 推送数据。
某种意义上来说它们区别在于数据的来源,一般来说 cold Observable 自己维护着数据,无论何时一个 Observer 的订阅的到来,都从头将数据推送到 Observer;而 hot Observable 的数据来源于外部,比如最经常用到的 fromEvent
将事件流转换为 Observable,即使没有 Observer 进行订阅,事件是时刻在发生着的,当 Observer 订阅时只能收到下一次的事件,且对多个 Observer 数据是共享的。
由于超出本文的范围,本文不会对 cold 和 hot 的概念做深入的解释,感兴趣的大家可以先看下官方解释。
如何使用 Marble 字符串
上面提到了 Marble 字符串的语法,那如何使用它们创建出对应的 Observable 呢?在使用 Marble Test
时,首先需要引入 rxjs/testing
的 TestScheduler
类,然后生成一个 TestScheduler
类实例 testScheduler,所有的操作都在实例的 run 方法中完成:
import {
expect } from "chai";
import {
TestScheduler } from "rxjs/testing";
describe("test hot observables", () => {
let testScheduler: TestSc