原文連接:RxJS: Avoiding takeUntil Leaks
原文做者:Nicholas Jamieson;發表於2018年5月27日
譯者:yk;如需轉載,請註明出處,謝謝合做!git
使用 takeUntil
操做符來實現 observable 的自動取消訂閱是 Ben Lesh 在 Don’t Unsubscribe 中所提出的一種機制。typescript
而該機制也是 Angular 中組件銷燬所採用的取消訂閱模式的基礎。安全
爲了使該機制生效,咱們必須以特定的順序調用操做符。而我最近卻發現有些人在使用 takeUntil
時,會由於操做符調用順序的問題而致使訂閱泄露。函數
讓咱們來看看哪些調用順序是有問題的,以及致使泄露的緣由。ui
若是在 takeUntil
後面調用這樣一個操做符,該操做符訂閱了另外一個 observable,那麼當 takeUntil
收到通知時,該訂閱可能不會被取消。spa
舉個例子,這裏的 combineLatest
可能會泄露 b
的訂閱。code
import { Observable } from "rxjs";
import { combineLatest, takeUntil } from "rxjs/operators";
declare const a: Observable<number>;
declare const b: Observable<number>;
declare const notifier: Observable<any>;
const c = a.pipe(
takeUntil(notifier),
combineLatest(b)
).subscribe(value => console.log(value));
複製代碼
一樣,這裏的 switchMap
也可能會泄露 b
的訂閱。cdn
import { Observable } from "rxjs";
import { switchMap, takeUntil } from "rxjs/operators";
declare const a: Observable<number>;
declare const b: Observable<number>;
declare const notifier: Observable<any>;
const c = a.pipe(
takeUntil(notifier),
switchMap(_ => b)
).subscribe(value => console.log(value));
複製代碼
當咱們用 combineLatest
靜態工廠函數來代替已廢棄的 combineLatest
操做符時,泄露的緣由會更加明顯,請看代碼:
import { combineLatest, Observable } from "rxjs";
import { takeUntil } from "rxjs/operators";
declare const a: Observable<number>;
declare const b: Observable<number>;
declare const notifier: Observable<any>;
const c = a.pipe(
takeUntil(notifier),
o => combineLatest(o, b)
).subscribe(value => console.log(value));
複製代碼
當 notifier
發出時,由 takeUntil
操做符返回的 observable 就算完成了,其訂閱也會被自動取消。
然而,因爲 c
的訂閱者所訂閱的 observable 並不是由 takeUntil
返回,而是由 combineLatest
返回,因此當 takeUntil
的 observable 完成時,c
的訂閱是不會被自動取消的。
在 combinedLast
的全部 observable 所有完成以前,c
的訂閱者都將始終保持訂閱。因此,除非 b
率先完成,不然它的訂閱就會被泄露。
要想避免這個問題,咱們就得把 takeUntil
放到最後調用:
import { combineLatest, Observable } from "rxjs";
import { takeUntil } from "rxjs/operators";
declare const a: Observable<number>;
declare const b: Observable<number>;
declare const notifier: Observable<any>;
const c = a.pipe(
o => combineLatest(o, b),
takeUntil(notifier)
).subscribe(value => console.log(value));
複製代碼
如此一來,當 notifier
發出時,c
的訂閱就會被自動取消。由於當 takeUntil
中的 observable 完成時,takeUntil
會取消 combineLatest
的訂閱,這樣也就依次取消了 a
和 b
的訂閱。
若是你正在使用 takeUntil
的機制來實現間接地取消訂閱,那麼你能夠經過啓用我添加到 rxjs-tslint-rules
包裏的 rxjs-no-unsafe-takeuntil
規則來確保 takeUntil
是 pipe
中的最後一個操做符。
一般的規定是將 takeUntil
放到最後。然而在有些狀況下,你可能須要把它放到倒數第二個的位置上。
在 RxJS 的操做符中,有一些是隻在源 observable 完成時纔會發出值的。就好比說 count
和 toArray
,只有在它們的源 observable 完成時,它們纔會發出源 observable 中數據的個數,或是其組成的數組。
當一個 observable 因 takeUntil
而完成時,相似 count
和 toArray
的操做符只有放在 takeUntil
後面纔會生效。
另外還有一個操做符是你須要放在 takeUntil
後面的,那就是 shareReplay
。
目前的 shareReplay
有一個 bug/feature:它永遠不會取消其源 observable 的訂閱,直到源 observable 完成,或是發生錯誤,詳見 PR。因此將 takeUntil
放在 shareReplay
後面是無效的。
上面提到的 TSLint 規則是知道這些例外的,因此你不用擔憂會形成一些莫名其妙的問題。
在 6.4.0 版本的 RxJS 中,shareReplay
作了必定修改,如今你能夠經過 config
參數來指定其引用計數行爲。若是你指定了 shareReplay
的引用計數,就能夠把它安全地放到 takeUntil
前面了。
想了解更多有關 shareReplay
的信息,請看這篇文章。