我需要在ngOnDestroy内“ complete()`takeUntil主题吗?

时间:2019-07-12 12:32:30

标签: angular typescript rxjs rxjs6

为了避免组件内部的可观察内存泄漏,在订阅Observable之前,我使用了takeUntil()运算符。

我在组件内部编写了以下内容:

private unsubscribe$ = new Subject();

ngOnInit(): void {
  this.http
    .get('test')
    .pipe(takeUntil(this.unsubscribe$))
    .subscribe((x) => console.log(x));
}

ngOnDestroy(): void {
  this.unsubscribe$.next();
  this.unsubscribe$.complete(); // <--- ??
}

最后我的问题是:

我需要写this.unsubscribe$.complete();还是next()足够?

unsubscribe$是否会被垃圾收集器抓住而没有完成?

请说明是否存在差异或无关紧要。我不希望我的组件出现内存泄漏。

3 个答案:

答案 0 :(得分:2)

简短的回答,不需要,但这也不会造成伤害。

长答案:

仅当它阻止垃圾收集时才需要取消订阅/按角度完成,因为订阅涉及某些主题,由于该主题的存在,该主题将超出要收集的组件。这就是造成内存泄漏的方式。

如果我有服务:

export class MyService {
  mySubject = new Subject();
}

(仅在根中提供)(这是一个单例,在实例化后将不会被销毁),以及一个注入此服务并订阅其主题的组件

export class MyLeakyComponent {
  constructor(private myService: MyService) {
    this.myService.mySubject.subscribe(v => console.log(v));
  }
}

这正在造成内存泄漏。为什么?因为MyLeakyComponent中的订阅是由MyService中的主题引用的,所以只要MyService存在并保留对它的引用,就无法对MyLeakyComponent进行垃圾回收,并且MyService在应用程序的生命期内将一直存在。每当您实例化MyLeakyComponent时,这都会变得复杂。要解决此问题,必须取消订阅或在组件中添加终止运算符。

但是此组件:

export class MySafeComponent {
  private mySubect = new Subject();
  constructor() {
    this.mySubject.subscribe(v => console.log(v));
  }
}

是完全安全的,将毫无问题地被垃圾收集。没有外部持久实体拥有对此的引用。这也是安全的:

@Component({
  providers: [MyService]
})
export class MyNotLeakyComponent {
  constructor(private myService: MyService) {
    this.myService.mySubject.subscribe(v => console.log(v));
  }
}

现在注入的服务由组件提供,因此该服务和组件将被一起销毁,并且可以安全地收集垃圾,因为外部引用也将被销毁。

这也是安全的:

export class MyHttpService { // root provided
  constructor(private http: HttpClient) {}

  makeHttpCall() {
    return this.http.get('google.com');
  }
}

export class MyHttpComponent {
  constructor(private myhttpService: MyHttpService) {
    this.myhttpService.makeHttpCall().subscribe(v => console.log(v));
  }
}

因为http调用是一类可终止的可观察变量,所以它们在调用完成后自然终止,因此无需手动完成或取消订阅,因为外部引用自然完成后就消失了。

关于您的示例: unsubscribe$主题位于组件本地,因此它不可能导致内存泄漏。任何本地学科都是如此。

关于最佳做法的说明: 可观察到的是复杂的。看起来完全安全的一个对象可能以微妙的方式涉及到外部主题。为了完全安全/如果您对可观察物不太满意,通常建议您退订所有非终止性可观察物。除了您自己花费的时间之外,没有其他缺点。我个人发现unsubscribe $信号方法很hacky,并认为它污染/混淆了您的流。对我来说最简单的是这样的:

export class MyCleanedComponent implements OnDestroy {
  private subs: Subscription[] = [];
  constructor(private myService: MyService) {
    this.subs.push(
      this.myService.mySubject.subscribe(v => console.log(v)),
      this.myService.mySubject1.subscribe(v => console.log(v)),
      this.myService.mySubject2.subscribe(v => console.log(v))
    );
  }

  ngOnDestroy() {
    this.subs.forEach(s => s.unsubscribe());
  }
}

但是,防止泄漏的唯一的BEST方法是尽可能使用angular提供的异步管道。它为您处理所有订阅管理。

答案 1 :(得分:0)

您只需要像unsubscribe一样

subscr: Subscription;

ngOnInit() {
 this.subscr = this.http
    .get('test')
    .pipe(takeUntil(this.unsubscribe$))
    .subscribe((x) => console.log(x));
}

ngOnDestroy() {
 this.subscr.unsubscribe();
}

使用unsubscibetakeUntil()

解决方案是使用takeUntil运算符构成我们的订阅,并使用在truthy中发出ngOnDestroy值的主题

export class AppComponent implements OnInit, OnDestroy {
  destroy$: Subject<boolean> = new Subject<boolean>();

  ngOnInit() {
   this.http
    .get('test')
    .pipe(takeUntil(this.unsubscribe$))
    .subscribe((x) => console.log(x));
  }

  ngOnDestroy() {
    this.destroy$.next(true);
  }
}

请注意,使用takeUntil之类的运算符代替手动取消订阅也将完成可观察对象,从而触发可观察对象上的任何完成事件。

请参见here

答案 2 :(得分:0)

我可以看到您正在订阅http响应。使用Angular HttpClient时有一件重要的事情:您不必取消订阅,因为它是自动完成的!

您可以使用finalize运算符进行测试-当可观察性完成时会调用它。 (并且当可观察对象完成时,它会自动退订)

$table_prefix = 'myprefix_';

如果您担心在HTTP请求仍在进行时您的组件可能会死掉,并且回调中的代码仍可能执行,则可以执行以下操作:

this.http
  .get('test')
  .pipe(takeUntil(this.unsubscribe$), finalize(() => console.log('I completed and unsubscribed')))
  .subscribe((x) => console.log(x));

也值得检查AsyncPipe

  

AsyncPipe自动为您订阅(和取消订阅)。