为 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
通知,它将取消订阅。 complete
和 unsubscribe
都不需要调用。而且您几乎从来没有真正想就某个主题致电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 既是可观察的(发送)又是观察者(收听),这里体现了二元性。 next
和 complete
都属于事物的“发送”端。 complete
表示“此流不会再发送任何值”。 unsubscribe
是“监听”界面的一部分,具有相应不同的含义:“不要再通知我任何进一步的排放”。
编辑:重新阅读我看到你已经在问题中包含了这个区别,所以我的回答可能不会对你增加太多:(。我认为语义很重要就其本身而言,足以在此处使用完全过度取消订阅,并且看不出在此模式中使用其中一个而不是另一个的实际风险。希望这仍然有些帮助!
【讨论】:
unsubscribe
和 complete
在语义上并不相同。 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 与简单取消订阅?