RXJS 如何知道 Finalize 何时完成?

RXJS How to Know When Finalize Completes?

我对 RXJS 比较陌生,所以如果重复,我深表歉意。我尝试搜索答案,但没有找到任何答案,这可能是因为我不知道要使用哪个搜索词。

我正在尝试了解如何知道服务调用的最终块何时完成,因为它会更新共享状态变量。

这是它的堆栈闪电战,不过我也会 post 下面的片段: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();
  });
}

我很惊讶地发现这个例子的输出是

Starting emissions

42

value of isLoading in next handler: true

100

value of isLoading in next handler: true

200

value of isLoading in next handler: true

Completing emissions

value of isLoading in complete handler: true

Value of isLoading in serviceCall finalize: false

为什么 serviceCallfinalizengOnInit 的订阅块的完整处理程序之后被调用?如果不是通过已完成的处理程序,我怎么知道 serviceCall 何时完成了对共享变量的操作?

关于finalize

这归结为 finalize 的实施方式。我同意这可能不是很直观。我属于一个分裂的派系,我相信它现在的实施方式 直观的方式。

考虑一个在发出任何东西之前取消订阅的可观察对象。我希望最终确定仍会触发,但我不希望将 complete 通知发送给我的观察者。

Six of one, half a dozen of the other

通常,流发生的最后一件事是取消订阅。取消订阅流时调用 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 的微任务队列中,你会注意到你的代码 运行s 的方式有明显的不同。在使用第一个值调用您的订阅之前,isLoading 已经是 false。

这是因为延迟之前的所有内容都是 运行 同步的,并且会在微任务队列 运行 之前完成。 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)
      })
    );
  });

}