当我开始使用 Angular 时,我工作场所的某人告诉我在使用
RxJs中的 observables 执行 API 调用时使用
take(1)
,声称它会在一次 API 调用后自动取消订阅。
所以我相信这个说法,并继续使用它,直到我发现组件加载时存在大量渲染延迟,尤其是在页面之间导航时。
让我们看一下我的例子:
这是一个
UserRepository
,它与 API 对话以向应用程序授予权限:
@Injectable({
providedIn: 'root'
})
export class UserRepository {
private apiUrl: string = environment.apiUrl;
constructor(private readonly httpRequestService: HttpRequestService) {
super();
}
@UnsubscribeOnDestroy()
getPermissions() {
return this.httpRequestService.get(`${this.apiUrl}/user/permissions`).pipe(map((res: any) => res.user));
}
}
用法:
userRepository.getPermissions().pipe(take(1)).subscribe(data => ...);
我用谷歌搜索了一下,发现
take(1)
不会自动取消订阅,而且它根本不是目的,它所做的只是从管道中获取一个可观察值并忽略其余部分。
要解决这个问题,我有两个选择:
takeUntil
与 unsubscriptionSubject
结合使用,致电 unsubscriptionSubject.next
触发取消订阅 ngOnDestroy
methodName => Subject
字典创建一个 BaseRepository,这样对于每个 API 调用,我都可以保存它自己的主题,并创建一个装饰器,自动取消订阅之前的 API 调用,并确保不会有超过一个订阅API 调用的类型。装饰器和用法示例:
基础存储库
export abstract class BaseRepository {
protected unsubscriptionSubjects: {[key: string]: Subject<void>} = {};
}
实施:
@Injectable({
providedIn: 'root'
})
export class UserRepository extends BaseRepository {
private apiUrl: string = environment.apiUrl;
constructor(private readonly httpRequestService: HttpRequestService) {
super();
}
@UnsubscribeOnDestroy()
getPermissions() {
return this.httpRequestService.get(`${this.apiUrl}/user/permissions`).pipe(map((res: any) => res.user));
}
}
装饰者:
export function UnsubscribeOnDestroy() {
return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
const originalMethod = descriptor.value;
descriptor.value = function (...args: any[]) {
// Access the instance of the class
const instance = this as any; // Cast to any to access custom properties
// Create a new Subject for the method
const methodName = propertyKey;
instance.unsubscriptionSubjects = instance.unsubscriptionSubjects || {};
if (instance.unsubscriptionSubjects[methodName]) {
instance.unsubscriptionSubjects[methodName].next();
} else {
instance.unsubscriptionSubjects[methodName] = new Subject();
}
// Invoke the original method
const result = originalMethod.apply(this, args);
// Return the original result
return result.pipe(takeUntil(instance.unsubscriptionSubjects[methodName]));
};
return descriptor;
};
}
我选择了选项#2,从那以后一切都得到了修复,渲染流畅,没有延迟。
我选择这个选项是因为我在同一页面加载大量数据而不刷新,所以当使用它几分钟执行大量搜索时,您可以看到加载状态慢慢变得滞后,并且一切都需要更多时间加载。因此,据我了解,理想的做法是在再次使用订阅时始终清除订阅。
我想问:
then
和 catch
那样内置只接受一个请求并自动取消订阅?对于你的问题2。你的解决方案非常好,我们必须这样做。 我们知道HTTP请求(使用HttpClient)是自动完成的(因为HTTP请求只发出一次然后完成)
所以几乎所有开发者都说不需要取消订阅/或者不需要添加take(1)。
但是,如果HTTP请求需要很长时间才能完成。你的做法很好。它帮助我们在 HTTP 请求完成之前取消订阅它。
导航至 /users,5 秒后离开。 正如您在离开用户组件 10 秒后所看到的。消息
Http 2 Response
和 Execute logic when HTTP 2 response
仍会写入控制台。
以我的经验。我建议我们应该始终取消订阅以避免意想不到的副作用
ngOnInit(): void {
this.http.get('https://jsonplaceholder.typicode.com/users').subscribe({
next: (res) => {
console.log('Http 1 Response ', res);
},
complete: () => {
console.log('Http 1 complete ');
},
});
this.http
.get('https://jsonplaceholder.typicode.com/users')
.pipe(delay(10000))
.subscribe({
next: (res) => {
console.log('Http 2 Response ', res);
console.log('Execute logic when http 2 response ');
},
complete: () => {
console.log('Http 2 complete ');
},
});
this.http
.get('https://jsonplaceholder.typicode.com/users')
.pipe(delay(10000), takeUntil(this.destroyed$))
.subscribe({
next: (res) => {
console.log('Http 3 Response ', res);
console.log('Execute logic when HTTP 3 response ');
},
complete: () => {
console.log('Http 3 complete ');
},
});
}