什么是 switchMap?
RxJS 中的 switchMap 操作符用于将一个 Observable 序列转换成另一个 Observable 序列。它接收一个函数参数,这个函数接收所订阅的源 Observable 发出的每个值,然后返回一个 Observable 序列,最终生成的 Observable 序列就是将源 Observable 序列中的每个值映射为一个新的 Observable 序列后,再将这些序列合并成一个新的 Observable 序列。
内部订阅问题
使用 switchMap,我们会发现在某些情况下,内部 Observable 序列的订阅可能无法取消。通常情况下,switchMap 会在一个新的值到来时取消之前的内部 Observable 序列的订阅,然后再订阅新的内部 Observable 序列。这是因为 switchMap 操作符要求内部 Observable 序列具有“可取消”的特性,但是在有些情况下,这个特性可能受到一些因素的影响,比如内部 Observable 序列有没有及时完成。
下面我们将具体探讨几种可能导致内部 Observable 序列无法取消的情况。
内部 Observable 序列不及时完成
在某些情况下,可能因为内部 Observable 序列没有及时完成,导致当前值被快速发射后就被丢弃,而内部 Observable 序列仍然在继续订阅执行,从而无法被取消。比如下面这个例子:
-- -------------------- ---- ------- ------ - --------- --------- - ---- ------- ------ - ---------- --------- - ---- ----------------- ----- ------- - ------------------- --------- ----- ------- - ------------- ------------ -- --------------- ----------------------------- --------- -- -------------------------------展开代码
在上面的代码中,我们通过 fromEvent 创建了一个 Observable 序列,其会在用户点击文档时发出一个新的值。而在 switchMap 中,我们将 click 事件转换成了一个 interval Observable 序列(每隔 100ms 发出一个新的值)。但是,我们也在 takeUntil 中订阅了点击文档事件,表示这个 Observable 序列在第一次点击文档时就应该被取消。
然而,实际上我们会发现,内部的 interval Observable 序列并没有被及时取消,它会一直发射新的值,直到用户再次点击文档。
这是因为 interval 本身是不会结束的,它会一直发射新的值。而在 switchMap 中,我们并没有对这个内部 Observable 序列进行任何操作,也就没有进行内部订阅的取消操作。因此,即使在外部 Observable 序列(也就是点击文档事件)发出 complete 时,内部 Observable 序列仍然会继续执行,发射新的值。
内部 Observable 序列本身就不支持取消
除了内部 Observable 序列没有及时完成会导致无法取消订阅外,还有一种情况是内部 Observable 序列本身就不支持取消。比如下面这个例子:
-- -------------------- ---- ------- ------ - --------- ------ --------- - ---- ------- ------ - ---------- --------- - ---- ----------------- ----- ------- - ------------------- --------- ----- ------- - ------------- ------------ -- -------- ------ ----------------------------- --------- -- -------------------------------展开代码
这里的 timer 操作符会创建一个 Observable 序列,它在指定的时间后发出一个值。也就是说,即使在外部 Observable 序列发出 complete 时,内部 Observable 序列仍然会发出值。因此,我们发现即使在第一次点击文档时,内部 Observable 序列仍然会一直执行,直到自身发出值。
怎样避免这种情况
以上两种情况都有一个共同的问题,那就是内部 Observable 序列没有及时完成。那么要解决这个问题,我们只需要让内部 Observable 序列尽早结束即可。
解决内部 Observable 序列没有及时完成的方法之一就是使用 takeUntil 操作符,它可以让内部 Observable 序列在某个外部 Observable 序列发出值时立即结束。比如,我们可以将之前的代码改为:
-- -------------------- ---- ------- ------ - --------- --------- - ---- ------- ------ - ---------- --------- - ---- ----------------- ----- ------- - ------------------- --------- ----- ------- - ------------- ------------ -- --------------- ----------------------------- -------------- ------ -- --------------------- ----- ---------- --------- --- -- -------------------------------展开代码
在这个例子中,我们在 takeUntil 操作符中使用了一个 tap 操作符,可以在 cancel 时提示一下。这样我们通过 tap 来判断是不是已经 takeUntil。在这里它的作用是,在外部 Observable 序列发出值时,内部 Observable 序列会立即使用 complete 结束。这样就可以避免内部 Observable 序列一直继续执行,而无法被及时取消的问题了。
总结
在使用 RxJS 中的 switchMap 操作符时,经常会遇到内部订阅无法取消的问题。这可能是因为内部 Observable 序列没有及时完成导致的,如果我们希望能够及时取消内部订阅,避免出现内部订阅无法取消的情况,我们需要尽早结束内部 Observable 序列,比如使用 takeUntil 操作符。
来源:JavaScript中文网 ,转载请注明来源 https://www.javascriptcn.com/post/648ad9dc48841e989491afca