30 天精通 RxJS (31):如何 Debug?
![RxJS Logo](https://images.cnblogs.com/cnblogs_com/blogs/796920/galleries/2324455/o_5acbd02d.png)
Debug 一直是 RxJS 的难题,原因是当我们使用 RxJS 后,代码就会变得高度 抽象化;实际上抽象并不是什么坏事,抽象会让代码显得简洁、干净,但同时也带来了除错上的困难。
在编写程序时,我们都会希望代码是简洁且可读的。 但当我们用简洁的代码来处理复杂的问题,就表示我们的代码会变得高度抽象!其实人类在思考复杂的问题都会偏好用抽象的方式来处理,例如说在下围棋时,常常说的棋形或是黑白哪一边的势比较好,这都是在抽象化处理问题。
RxJS 如何调试?
do
在 RxJS 的世界中,有一个 Operator 叫作do
,它不会对元素产生任何影响,在实务上很常用来做错误的追踪,如下
const source = Rx.Observable
.interval(1000)
.take(3)
const example = source
.do((x) => console.log('do log: ' + x))
.map((x) => x + 1)
example.subscribe((x) => {
console.log('subscription log: ' + x)
})
// do log: 0
// subscription log: 1
// do log: 1
// subscription log: 2
// do log: 2
// subscription log: 3
从上面的例子可以看出来,我们可以传入一个 callback function 给do
,我们可以在 do 的内部对元素作任何操作(像是 log),但不会对元素产生影响。 这很适合用在检测每一步送出的元素是否符合我们的预期。
do(...)
的行为跟本质上是一样的map(x => { ... return x; })
Observable 间的关联图
当程序有点复杂时,我们最好是能先画出 Observable 与 Observable 之间的关联,在厘清各个 Observable 间的关系后,我们就能更轻易地找出问题在哪。 范例如下
const addButton = document.getElementById('addButton')
const minusButton = document.getElementById('minusButton')
const state = document.getElementById('state')
const addClick = Rx.Observable.fromEvent(addButton, 'click')
const minusClick = Rx.Observable.fromEvent(minusButton, 'click')
const initialState = Rx.Observable.of(0)
const numberState = initialState
.merge(addClick.mapTo(1), minusClick.mapTo(-1))
.scan((origin, next) => origin + next)
numberState.subscribe({
next: (value) => {
state.innerHTML = value
},
error: (err) => {
console.log('Error: ' + err)
},
complete: () => {
console.log('complete')
},
})
上面这段代码,我们可以把关联图画成以下的样子
-------------- -------------- --------------
' ' ' ' ' '
'initialState' ' addClcik ' ' minusClick '
' ' ' ' ' '
-------------- -------------- --------------
| | |
| | mapTo(1) | mapTo(-1)
merge | ____________________| |
| \__________________________________________|
|
\|/
|
| scan((origin, next) => origin + next)
|
\|/
-------------
' '
'numberState'
' '
-------------
把每个一 observable 对象都框起来,并画出之间的关联,以及中间使用到的 Operators,这样一来我们就能够很清楚的了解这段代码在做什么,以及如何运作。 最后我们只要在每一个环节去确认送出的元素就能找出错误出现在哪里。
Marble Diagram
在理清每个 observable之间的关系并找出问题出现在哪个环节后,我们只要画出该环节的 Marble Diagram 前后变化就能清楚地知道问题是如何发生。 接续上面的例子,如果今天问题出在merge()
之后,那我们就把merge()
前后的 Marble Diagram 画出来
initialState: 0|
addClick : ----------1---------1--1-------
minusClick : -----(-1)---(-1)---------------
merge(...)
: 0----(-1)-1-(-1)----1--1-------
scan((origin, next) => origin +next)
numberState : 0----(-1)-0-(-1)----0--1-------
到这里我们应该就能清楚地知道问题出在哪,最后就只要想如何解决问题就行了。
如果还是不知道问题在哪,很有可能是 Marble Diagram 画错,可以再利用
do
进行检查
只要照着以上三个步骤做除错,基本上就不用担心会有解决不了的错误,但是这三个步骤仍然显得太过繁琐,或许我们应该做一个工具来简化这整个流程!
RxJS Devtools
RxJS Devtools 是我跟我的好友 Jerry Lin 共同开发的 Chrome Extension,目前还在 preview 阶段,很多 feature 还没有实作但基本的功能已经能动了,使用方式很简单如下
Observable.prototype.debug = window.rxDevTool(Observable)
首先我们的 extension 会在 window 底下塞入一个方法叫rxDevTool
,所以开发者只要传入 Observable 并把这个rxDevTool
的回传值塞到Observable.prototype.debug
就能使用debug
了。
Observable.interval(1000)
.take(5)
.debug('source1')
.map((x) => x + 1)
.debug('source2')
.subscribe(function () {
//...
})
这个debug()
跟do()
一样,不会对元素造成任何影响,但不同的是要传入的参数是开发者自订的名称,代表当前的 observable,这时在 Chrome 的开发者工具中切到 RxJS 的 tab 页就能看到debug()
自动画出 Marble Diagram,如下图
送出元素是对象也行喔!
![](https://img2024.cnblogs.com/blog/2628787/202404/2628787-20240422230605927-612764084.webp)
目前 RxJS Devtools 已经能够自动画出 Marble Diagram,也能做到类似 do 的功能(放在第二个参数),之后会希望能够自动画出 observable 之间的关联图,这样一来我们在做 RxJS 的除错时就会方便非常多!
等到 RxJS Devtools 正式 release 后,会在专门写一篇文章介绍如何使用。
结语
这篇文章主要在讲述我们使用 RxJS 后要如何进行调试,基本上只要照着以下三个步骤就能找出问题
- 善用检查送出的元素do()
- 画出 observable 之间的关联图
- 画出关键环节前后的 Marble Diagram
最后简单的介绍了 RxJS Devtools 的使用方式与功能,也请期待 RxJS Devtools 的正式发布。
本系列仅作为学习记录所用,摘录自30 天精通 Rxjs!强烈推荐!膜拜大佬!