【问题标题】:Should all RxJS Subscription need to be unsubscribed?是否应该取消所有 RxJS 订阅?
【发布时间】:2021-09-30 04:39:30
【问题描述】:

我在 Angular 组件中有以下代码来捕获 keyup 事件并在发生这种情况时做出响应。用户可以离开页面,然后返回并执行相同的操作数百次。

    fromEvent(this.input?.nativeElement, 'keyup')
      .pipe(
        pluck<unknown, string>('target', 'value'),
        filter((searchTerm: string) => (searchTerm?.length > 2 || searchTerm?.length == 0)),
        throttleTime(200),
        debounceTime(300),
        distinctUntilChanged()
      )
      .subscribe(search => {
        this.setPageIndex();
        this.TriggerLoadUsers(search, 'asc', 0, 10);
      });

这是另一种模式,其中Subscription 的显式分配完成,然后在角度生命周期方法的ngOnDestroy 中取消订阅。

public keyupEventsSub$!: Subscription;

this.keyupEventsSub$ = fromEvent(this.input?.nativeElement, 'keyup')
      .pipe(
        pluck<unknown, string>('target', 'value'),
        filter((searchTerm: string) => (searchTerm?.length > 2 || searchTerm?.length == 0)),
        throttleTime(200),
        debounceTime(300),
        distinctUntilChanged()
      )
      .subscribe(search => {
        this.setPageIndex();
        this.TriggerLoadUsers(search, 'asc', 0, 10);
      });

this.keyupEventsSub$.unsubscribe();
  1. 遵循明确分配Subscription 的第二种模式subscribedunsubscribed 是否有优势?
  2. 对任何Observable 订阅使用相同的模式是否有任何副作用?
  3. 是否存在不需要显式赋值的更好模式?

【问题讨论】:

    标签: rxjs


    【解决方案1】:

    1.) 是的,所有订阅都应该取消订阅以防止内存泄漏。您不必取消订阅 Http 调用或路由器事件,因为它们已经完成,Angular 会为我们处理,但我个人仍然取消订阅所有订阅。

    2.) 对任何可观察订阅使用相同的模式没有副作用。有很多模式,我会在最后展示。

    3.) 有一个更好的模式,我会从最不喜欢到最喜欢。

    直接订阅分配。这样做的缺点是每个可观察的流都有许多订阅变量,因此可能会失控。

    // Direct subscription variable (What you have shown)
    // don't put a dollar at the end of subscription variable because
    // it is a subscription and not an observable
    public subscription!: Subscription;
    ....
    this.subscription = this.observable$.subscribe(...);
    ...
    ngOnDestroy(): void {
      this.subscription.unsubscribe();
    }
    

    订阅数组: 在数组中添加每个订阅。

    public subscriptions!: Subscription[];
    ...
    this.subscriptions.push(this.observable$.subscribe(...));
    ...
    ngOnDestroy(): void {
      this.subscriptions.forEach(subscription => subscription.unsubscribe());
    }
    

    异步管道: 我的最爱之一,但只能在 HTML 中呈现数据时使用,而不是用于事件监听器(本质上意味着每次发出 observable 时都会做出反应)。 当视图呈现时,observable 将自动被订阅,一旦视图被销毁,订阅就会被取消订阅。

    count$ = this.otherObservable$.pipe(map(data => data.count));
    ...
    <h1>{{ count$ | async }}</h1>
    

    破坏主题: 我最喜欢的另一个,这个对 TypeScript 类中的订阅很有用(用于事件侦听器)。这个的美妙之处在于不会创建太多变量,并且您不必处理数组。

    import { Subject } from 'rxjs';
    import { takeUntil } from 'rxjs/operators';
    ....
    private destructionSubject$ = new Subject<void>();
    ...
    observable$.pipe(
      takeUntil(this.destructionSubject$),
    ).subscribe(...);
    observable2$.pipe(
      takeUntil(this.destructionSubject$),
    ).subscribe(...);
    ...
    ngOnDestroy(): void {
      this.destructionSubject$.next();
      this.destructionSubject$.complete();
    }
    

    如果您只关心第一次发射而不是后续发射,还有另一种方法:

    这可用于事件侦听器(每次此 observable 发出时做出反应)。这将进行第一次发射并自动取消订阅(订阅失效)。

    import { take } from 'rxjs/operators';
    ....
    observable$.pipe(take(1)).subscribe(...);
    

    我希望我回答了您的所有问题,并为您提供了退订的好方法。

    【讨论】:

    • 您是否考虑在以下代码中使用tap 是另一种具有相同好处this.route.firstChild.paramMap.pipe( take(1), tap(map=&gt;this.getRouteParams(map)) ).subscribe(); 的方法?
    • 我已经编辑了我的答案以包含take(1) 模式。是的,如果您只关心第一次发射,那么take(1) 方法很好。
    • 这个答案中简要提到的一个概念是“one and done” observables,但它涉及到一个关键点。在某些情况下,可观察对象可以发送“完成”信号。当使用 Angular 的 http 服务时,它会返回一个带有响应数据的 observable,然后发出“完成”。这会导致所有订阅者自动取消订阅。take() 运算符在收到发出的计数后发送一个“完成”事件值。重要的是要知道并非所有的 observables 都是完整的,因此请进行研究,如有疑问,请始终退订。
    【解决方案2】:

    是否应该取消所有 RxJS 订阅?

    只有 Observables 可能永远不会 errorcomplete 需要退订。如果您不确定,退订会更安全。

    from(promise) 保证完成或出错。
    from(['a','r','r','a','y']) 保证完成。
    of(...) 保证完成。
    EMPTY 保证完成。
    @ 987654328@ 永远不会完成或失败。
    fromEvent(...) 可能永远不会完成或失败。
    http.get(...) 一个编写良好的 http 客户端应该始终完成或失败,但有一些(出于各种技术原因)不不。如果您不确定,请退订。

    如何退订

    一般来说,隐式优于显式。当满足特定条件时,有各种运营商会为您退订。

    take
    takeWhile
    takeUntil

    是其中最受欢迎的 3 个。比起在我们的代码中的某处粘贴stream.unsubscribe(),更喜欢它们。

    这样做可以将有关您的 observable 的所有逻辑保存在一个地方。随着您使用的 observable 数量的增加,维护/扩展变得相当容易。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2019-03-16
      • 1970-01-01
      • 2017-03-26
      • 2020-10-28
      • 2017-03-20
      • 2019-09-07
      • 2017-09-14
      • 2021-11-18
      相关资源
      最近更新 更多