是否有必要在后端服务中 unsubscribe/complete 以防止副作用?

Is it necessary to unsubscribe/complete in a backend service to prevent side effects?

关于何时以及如何取消订阅以及订阅不应泄漏到服务之外的问题已经进行了很多讨论。我已经阅读了很多关于这个问题的文章,我想我理解了一些部分,但是我在文章和教程中缺少的是如何处理我们创建的 "new subscription pattern"。

所以我先解释一下我在说什么概念,然后再提出问题。


概念:

//some.component.ts(极度简化)

this.someSub = this.backendService.someStuff.subscribe(...);

ngOnDestroy() {
  this.someSub.unsubscribe(); // I hope we agree that it is best practice to unsubscribe here, but anyway this is not the question
}

// backend.service.ts

export class BackendService {
  private _someStuff = new BehaviorSubject<any>(null);
  readonly someStuff = this._someStuff.asObservable();

  constructor(http: HttpClient) { }

  getStuff() {
    this.http.get('...').subscribe( response => this._someStuff.next(response) );

  }

问题:

以上是我从调查中找到的一个很好的解决方案,它不会将 HTTP 订阅泄露到服务之外,但是也有很多关于您应该取消订阅 HTTP 可观察对象的事实的讨论,即使它们会自行完成.所以我认为服务也是如此。 但是我从来没有看到服务中的 HTTP 请求被取消订阅,主题也没有完成。 由于我认为这是必要的,因此下面显示了 backenService 我期望的结果:

// backend.service.ts

export class BackendService implements OnDestroy {                      // A. //
  httpSubs = new Subscription();                                        // B1. //

  private _someStuff = new BehaviorSubject<any>(null);
  readonly someStuff = this._someStuff.asObservable();

  constructor(http: HttpClient) { }

  getStuff() {
    this.httpSubs.add(this.http.get('...')                              // B2. //
      .subscribe( response => this._someStuff.next(response) )
    );     
  }

  // rest of CRUD implementations
  // ...

  ngOnDestroy(): void {                                                               
    this.httpSubs.unsubscribe();                                        // B3. //
    this._someStuff.complete();                                         // C. //
  }

我标记的东西 (A.-C.) 与我在普通教程和东西中看到的不同。
如果我没有遗漏关于组件、服务以及缺少取消订阅如何可能导致副作用的一些基本知识……那么我关于服务中是否需要 unsubscirbe/complete 的问题是:

  1. 我希望现在我们在服务中有订阅,必须在这里实现 OnDestroy (A.),就像我们在组件中所做的那样才能正确退订。 我想即使在根中提供服务时也是如此(因此将作为应用程序的最后一部分之一销毁),因为该服务可能会对另一个根服务产生副作用,而另一个根服务也不是垃圾尚未收集。
    这是正确的吗?

  2. 如果我们不在服务中取消订阅,我们将面临与不在组件中取消订阅相同的问题。因此添加了 B 1 - 3(实际上如何取消订阅的类型并不重要 - 只有我们这样做 - 我选择原生 RxJS 方法)
    这是正确的吗?

(旁注:我知道在某些情况下不取消订阅和 handle the side effects manually 可能是更好的方法,但让我们忽略这一点,这样问题就不会出现广泛)

  1. 我不知道 .asObservable 内部是如何工作的,但也必须有一些订阅。
    那么有必要完成像_someStuff(C.)这样的行为科目(或普通科目)吗?

如果 observable(例如:来自 HttpClient)完成,不需要取消订阅。

如果可观察对象的来源完成发出一个错误,来源将自动取消订阅

例如,当你有这样的东西时

http.get(...)
 .pipe(
  a(),
  b(),
  c(),
 ).subscribe(observer)

幕后发生了一些有趣的事情。首先 observer 被转换成 subscriberSubscriber class 扩展 Subscription,它包含 unsubscription logic.

链中的每个运算符(abc)都会有自己的 Subscriber 实例,因为在第一个 subscribe 之后,它们的运算符返回的每个可观察值也将被订阅(运算符是一个接收可观察值和 returns 可观察值的函数)。你最终得到的是一串 Subscribers.

// S{n} -> Subscriber {n} (in the order in which they are created)
http.get(...)
 .pipe(
  a(), // S4
  b(), // S3
  c(), // S2
 ).subscribe(observer) // S1

S1S2 的父级(目的地),S2S3 的父级等等.

HttpClient.get 本质上与

相同
new Observable(subscriber => {
 // Make request

 // On request ready
 subscriber.next(resultOfTheRequest)
 subscriber.complete(); // So here completes!
})

Source Code

值得一提的是,在这种情况下,subscriber 参数将为 S4

subscriber.complete() 等同于 S4.complete()。发生这种情况时,S4 会将 完整通知 传递给 S3,后者又会将通知传递给 S2,依此类推,直到S1 收到通知。此时会取消订阅.

当订阅者取消订阅时,其所有后代也将取消订阅。

因此,取消订阅已关闭的订阅者(取消订阅后变为closed)是无害的,但是多余

1.

如果在根级别提供服务(例如:providedIn: 'root'),则不会调用其 ngOnDestroy。据我所知,只有当服务被销毁时才会调用它,当您在 组件级别 .

提供它时就是这种情况

Here's 说明这一点的演示。

IMO,如果该服务是在根级别提供的,您不必担心取消订阅可观察对象。但是,如果您的服务是在组件级别提供的,您应该退订。

2.

如前几节所述,如果您的 Observable 已完成,那么无论您的服务在何处提供,您都无需担心取消订阅。

如果您的 observables 不自己完成(例如fromEvent)并且 未提供 [=] 97=]根级别,您应该在ngOnDestroy中手动退订。

3.

A Subject 将维护 活跃订阅者列表 。当活跃订阅者变得 不活跃 时,它将从订阅者列表中删除。

例如,如果您在根提供的服务中有一个 BehaviorSubject,您不必担心会丢弃它的引用。

实际上,更安全 的方法是调用 Subject.unsubscribe()Subject.complete() 将向其订阅者发送完整的通知,然后清空列表。