问题描述
我对 RXJS 比较陌生,所以如果这是重复的话,我深表歉意。我尝试搜索答案,但找不到任何答案,可能是因为我不知道要使用哪些搜索词。
我试图了解如何知道服务调用的 finalize 块何时完成,因为它更新了共享状态变量。
这是它的stackblitz,虽然我也会在下面发布片段:https://stackblitz.com/edit/angular-ivy-xzvkjl
我有一个 Angular 应用程序,其服务将共享 isLoading
标志设置为 true,启动 HTTP 请求,然后使用 finalize
将 isLoading
标志设置回 false,这样无论成功或错误,检查 isLoading
标志的项目都知道 HTTP 请求不再处理。
我已将该场景简化为单独的方法而不是单独的类:
isLoading = false;
public ngOnInit() {
this.serviceCall().subscribe(
next => {
console.log("value of isLoading in next handler: " + this.isLoading);
},err => {
console.log("value of isLoading in error handler: " + this.isLoading);
},() => {
console.log("value of isLoading in complete handler: " + this.isLoading);
}
);
}
private serviceCall() {
this.isLoading = true;
return this.httpCall().pipe(
tap(value => console.log(value)),finalize(() => {
this.isLoading = false;
console.log("Value of isLoading in serviceCall finalize: " + this.isLoading);
})
);
}
private httpCall() {
return new Observable(subscriber => {
console.log("Starting emissions");
subscriber.next(42);
subscriber.next(100);
subscriber.next(200);
console.log("Completing emissions");
subscriber.complete();
});
}
我惊讶地发现这个例子的输出是
开始排放
42
下一个处理程序中 isLoading 的值:true
100
下一个处理程序中 isLoading 的值:true
200
下一个处理程序中 isLoading 的值:true
完成排放
完整处理程序中 isLoading 的值:true
serviceCall finalize 中 isLoading 的值:false
为什么在 finalize
的订阅块的完整处理程序之后调用 serviceCall
的 ngOnInit
?如果不是通过已完成的处理程序,我如何知道 serviceCall
何时完成了对共享变量的操作?
解决方法
关于finalize
这归结为 finalize
的实现方式。我同意这也许不是很直观。我属于分裂派的一部分,他们认为现在实施的方式是直观的方式。
考虑一个在发出任何内容之前取消订阅的 observable。我希望最终确定仍会触发,但我不希望向我的观察者发送 complete
通知。
六个一个,另一个六个
通常,流发生的最后一件事是它被取消订阅。取消订阅流时调用 Finalize。这发生在完整或错误发射之后。
您可以将 finalize 视为可观察对象的拆卸过程中发生的事情。而观察者正在观察一个仍然存在的可观察对象的发射。
尽可能避免副作用
一般来说,设置全局变量并稍后在同一个 pipeline
中检查它们等副作用被视为代码异味。相反,如果你更倾向于 RxJS 流提倡的函数式方法,这样的问题应该会消失。
快速搁置:
定时异步事件通常会导致奇怪或意外的结果(如果你能帮忙的话,你真的不应该手动实现这种事情的部分原因)。
考虑一下当我在你的流中添加延迟时会发生什么:
private serviceCall() {
this.isLoading = true;
return this.httpCall().pipe(
tap(value => console.log(value)),finalize(() => {
this.isLoading = false;
console.log("Value of isLoading in serviceCall finalize: " + this.isLoading);
}),delay(0)
);
}
您会认为 0 毫秒的延迟应该没什么区别,但是因为每个延迟都放在 JS 的微任务队列中,您会注意到代码运行方式的显着差异。在使用第一个值调用您的订阅之前,isLoading
已经是假的。
那是因为延迟之前的一切都是同步运行的,并且会在微任务队列运行之前完成。 delay(0)
之后的所有内容都是异步运行的,并将在下一次 JS 准备好运行微任务队列时完成。
阻力最小的解决方案
这不是惯用的 RxJS,但在这种情况下,它会按照您期望的 finalize 方式工作。
您可以使用 tap 运算符来捕获 complete
发射。由于点击将在 complete
上触发,因此它应该在 subscribe
之前触发,因此适用于您的用例。
function serviceCall() {
const setIsLoading = bool => (_ = null) => this.isLoading = bool;
return defer(() => {
setIsLoading(true)();
return this.httpCall().pipe(
tap({
next: console.log,error: setIsLoading(false),complete: setIsLoading(false)
})
);
});
}