取消订阅服务内部的 Observables?
Unsubscribing from Observables inside services?
在查看代码示例时,我经常看到服务内部的可观察对象未取消订阅的情况。
这是一个例子:
export class AuthGuard implements CanActivate {
private isLoggedIn: boolean;
private isLoggedIn$: Observable<boolean>;
constructor(private authService: AuthService, private router: Router) {
this.isLoggedIn$ = this.authService.isLoggedIn();
this.isLoggedIn$.subscribe(res => {
if (res) {
this.isLoggedIn = true;
}
else {
this.isLoggedIn = false;
}
});
}
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
if (this.isLoggedIn) {
return true;
}
else {
this.router.navigate(['login']);
return false;
}
}
}
在这种情况下,您是否有理由不取消订阅 observable this.isLoggedIn$?或者上面的例子只是糟糕的编码导致内存泄漏?
对于核心服务来说,取消订阅并不是真正必要的,因为服务与应用程序一样存在,而且服务是单例的。问题是你必须确保你总是将它作为单例使用(为了更好的解决方案,请阅读下文)
对于组件服务,取消订阅应该放在服务的 ngOnDestroy
中,因为服务实际上可以实现这个 NgOnDestroy
钩子。更好的方法是使用 takeUntil(this.destroy)
管道并在销毁时发出它。
更好的方法是在模板中使用 async
管道,永远不要直接订阅这些内容。
另一方面,在你的守卫中,你可以使用 take(1)
管道,这将接收第一个发射并立即取消订阅,而无需你这样做,这会将你的示例更改为此。可以看到,代码里面没有subscribe:
export class AuthGuard implements CanActivate {
constructor(private authService: AuthService, private router: Router) {}
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
return this.authService.isLoggedIn().pipe(
take(1),
tap((loggedIn) => {
if (!loggedIn) {
this.router.navigate(['login'])
}
})
);
}
}
最重要的是,尝试使用模板内部异步和 rxjs 管道函数让您的应用程序保持 Observable 流,而不是订阅和存储子结果。要使 isLoggedIn()
在订阅时发出最后一个结果,您可以使用 shareReplay()
管道或使其成为 BehaviourSubject
开始。
When looking at code examples, I often see the case where observables inside services are not unsubscribed from.
您正在查看一个错误。
Is there a reason why you would not unsubscribe from the observable this.isLoggedIn$ in this case?
如果你想泄漏内存。
Or is the above example just bad coding leading to memory leaks?
是的,这是内存泄漏。 this
引用的对象销毁后,订阅函数会继续执行。如果这个对象永远不会被销毁,它可能永远不会发生,但它是一个代码示例,它会在临时创建对象的单元测试中失败。
用 @Injectable()
标记的对象通常表现得像单例或具有弱引用。它可能会工作一段时间,但是一旦你在临时情况下使用它,它就会泄漏内存。
在查看代码示例时,我经常看到服务内部的可观察对象未取消订阅的情况。
这是一个例子:
export class AuthGuard implements CanActivate {
private isLoggedIn: boolean;
private isLoggedIn$: Observable<boolean>;
constructor(private authService: AuthService, private router: Router) {
this.isLoggedIn$ = this.authService.isLoggedIn();
this.isLoggedIn$.subscribe(res => {
if (res) {
this.isLoggedIn = true;
}
else {
this.isLoggedIn = false;
}
});
}
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
if (this.isLoggedIn) {
return true;
}
else {
this.router.navigate(['login']);
return false;
}
}
}
在这种情况下,您是否有理由不取消订阅 observable this.isLoggedIn$?或者上面的例子只是糟糕的编码导致内存泄漏?
对于核心服务来说,取消订阅并不是真正必要的,因为服务与应用程序一样存在,而且服务是单例的。问题是你必须确保你总是将它作为单例使用(为了更好的解决方案,请阅读下文)
对于组件服务,取消订阅应该放在服务的 ngOnDestroy
中,因为服务实际上可以实现这个 NgOnDestroy
钩子。更好的方法是使用 takeUntil(this.destroy)
管道并在销毁时发出它。
更好的方法是在模板中使用 async
管道,永远不要直接订阅这些内容。
另一方面,在你的守卫中,你可以使用 take(1)
管道,这将接收第一个发射并立即取消订阅,而无需你这样做,这会将你的示例更改为此。可以看到,代码里面没有subscribe:
export class AuthGuard implements CanActivate {
constructor(private authService: AuthService, private router: Router) {}
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
return this.authService.isLoggedIn().pipe(
take(1),
tap((loggedIn) => {
if (!loggedIn) {
this.router.navigate(['login'])
}
})
);
}
}
最重要的是,尝试使用模板内部异步和 rxjs 管道函数让您的应用程序保持 Observable 流,而不是订阅和存储子结果。要使 isLoggedIn()
在订阅时发出最后一个结果,您可以使用 shareReplay()
管道或使其成为 BehaviourSubject
开始。
When looking at code examples, I often see the case where observables inside services are not unsubscribed from.
您正在查看一个错误。
Is there a reason why you would not unsubscribe from the observable this.isLoggedIn$ in this case?
如果你想泄漏内存。
Or is the above example just bad coding leading to memory leaks?
是的,这是内存泄漏。 this
引用的对象销毁后,订阅函数会继续执行。如果这个对象永远不会被销毁,它可能永远不会发生,但它是一个代码示例,它会在临时创建对象的单元测试中失败。
用 @Injectable()
标记的对象通常表现得像单例或具有弱引用。它可能会工作一段时间,但是一旦你在临时情况下使用它,它就会泄漏内存。