为 takeUntil 运算符提供的杀死 observable 的更好方法是啥,为啥?

Posted

技术标签:

【中文标题】为 takeUntil 运算符提供的杀死 observable 的更好方法是啥,为啥?【英文标题】:What approach is better for killing an observable, provided for takeUntil operator, and why?为 takeUntil 运算符提供的杀死 observable 的更好方法是什么,为什么? 【发布时间】:2019-11-16 10:28:03 【问题描述】:

我有一个关于使用 Angular 和 RxJs 的 takeUntil 运算符取消订阅的常见模式之一的问题。 在this article 中,它位于第三位之下。 比如我们在一个组件类中有这样的代码:

  private destroy$: Subject<boolean> = new Subject();

  ngOnInit() 
     this.control.
     .pipe(takeUntil(this.destroy$)
     .subscribe(doSmthngFunc); 
  

  ngOnDestroy() 
    this.destroy$.next(true);
    // Which next line of code is correct?
    // this.destroy$.complete()     // this one?
    // this.destroy$.unsubscribe()  // or this one?
  

第一行 this.destroy$.next(true) 是完全清楚的。 但第二个不是。如果我们研究这些方法的实现,我们会发现它们的行为有些相似。 complete(): unsubscribe():

据我所知,在语义上 complete() 更可取,因为我们在组件生命周期中第一次和最后一次调用 next(),然后我们完成了这个 Subject,被视为 Observable 并且可以调用 complete()。这些方法属于观察者,取消订阅属于可观察对象,我们没有要取消订阅的订阅。 但在底层,这些方法有类似的代码:

    this.isStopped = true; // both

    this.observers.length = 0; // complete
    this.observers = null;     // unsubscribe

    this.closed = true;        // only unsubscribe

理论上,complete() 具有延迟效果,因为它可能会在每个订阅的观察者上调用 complete(),但我们在 destroy$ 上没有观察者。 那么问题来了——哪种方式更可取,更不容易出错,为什么?

【问题讨论】:

Which next line of code is correct? 注释下方的两行都是多余的。只有this.destroy$.next(true); 是必需的。 takeUntil 操作员是destroy$ 主题的唯一订阅者,一旦destroy$ 发出next 通知,它将取消订阅。 completeunsubscribe 都不需要调用。而且您几乎从来没有真正想就某个主题致电unsubscribe - 请参阅blog.angularindepth.com/rxjs-closed-subjects-1b6f76c1b63c but we have no observers on destroy$ 每次使用 takeUntil() 时都会添加一个观察者。观察者是监听的事物。我想你误解了这个词。 上面指定的博客文章链接不再有效。该帖子的规范链接是:ncjamieson.com/closed-subjects 【参考方案1】:

组件的破坏是一个单一的事件。

   this.destroy$.next();
   this.destroy$.complete();

确保主题只发出一次并且完成

例如;

    const destroy$ = new Subject();

    destroy$.subscribe(v => console.log("destroyed"));

    destroy$.next();
    destroy$.complete();
    destroy$.next();

    // the above prints "destroyed" only once.

完成不是技术要求,但如果不这样做,则依赖于完成而不是发射的业务逻辑将不会总是有效,并且可能会泄漏内存。

例如,以下将是 RxJs 中的内存泄漏。

   destroyed$.subscribe(() => 
       console.log('This might leak memory');
   );

上面的内容可能会泄漏内存,因为订阅永远不会结束,并且 observable 永远不会完成。您可以通过添加 first() 运算符或确保主题已完成来修复泄漏。 RxJS 不知道主题只会发出一个值,所以你必须告诉它。否则订阅者仍然绑定到堆栈帧并且不会被垃圾收集。因此,虽然垃圾收集器可能会在组件使用后收集组件,如果有任何引用订阅者的堆栈帧,那么订阅会继续存在。

所以在你的销毁对象上调用 complete,这样其他人就不会犯错。

this.destroy$.unsubscribe()

在主题上调用 unsubscribe 可能不会对创建 inner 订阅的下游操作员产生影响。例如,switchMap()mergeMap() 创建内部订阅。

因此,您无法有效地管理更高级别的订阅。最好取消订阅调用subscribe() 方法时创建的订阅,因为这是运算符链中的最后一个。

【讨论】:

【参考方案2】:

这两个语句都会产生预期的效果。 Afaik 没有技术理由不使用unsubscribe。语义上——正如你所提到的——使用complete 更有意义。

Subject 既是可观察的(发送)又是观察者(收听),这里体现了二元性。 nextcomplete 都属于事物的“发送”端。 complete 表示“此流不会再发送任何值”。 unsubscribe 是“监听”界面的一部分,具有相应不同的含义:“不要再通知我任何进一步的排放”。

编辑:重新阅读我看到你已经在问题中包含了这个区别,所以我的回答可能不会对你增加太多:(。我认为语义很重要就其本身而言,足以在此处使用完全过度取消订阅,并且看不出在此模式中使用其中一个而不是另一个的实际风险。希望这仍然有些帮助!

【讨论】:

unsubscribecomplete 在语义上并不相同。 Complete 将传播给子观察者,但 unsubscribe 将简单地停止发射。您的回答意味着您可以使用 unsubscribe 而不会产生副作用,但我认为这不是真的。 github.com/ReactiveX/rxjs/blob/… 我的意思是它们在语义上根本不一样。我提倡在这里使用完整的。当您说取消订阅会导致副作用时,您是什么意思?在这种特殊情况下,我的意思是。【参考方案3】:

我认为您需要做的不仅仅是destroy$.next();destroy$ 是您在课堂上创建的Subject,它唯一负责的是中止您的订阅。没有人,但你的班级可以触摸它(因为它是私有的)

根据文章,最好做一个destroy$.complete(),以避免内存泄漏。我认为在上面使用unsubscribe() 没有任何意义。如果有人订阅了destroy$,您应该将其存储在订阅中,并在调用者ngOnDestroy()-方法中取消订阅。但是,由于您使用的是takeUntil,因此无需取消订阅。

【讨论】:

以上是关于为 takeUntil 运算符提供的杀死 observable 的更好方法是啥,为啥?的主要内容,如果未能解决你的问题,请参考以下文章

撤消 takeUntil 对触发 rxjs 中另一个动作的影响

Angular + RxJS:使用 takeUntil 与简单取消订阅?

RxJS迁移5到6 - 取消订阅TakeUntil

[RxJS] Stopping a Stream with TakeUntil

typescript takeuntil.ts

RxJS 迁移 5 到 6 - 使用 TakeUntil 取消订阅