【问题标题】:RXJS How to Know When Finalize Completes?RXJS 如何知道 Finalize 何时完成?
【发布时间】:2021-04-25 03:26:33
【问题描述】:

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

我试图了解如何知道服务调用的 finalize 块何时完成,因为它更新了共享状态变量。

这是它的堆栈闪电战,虽然我也会在下面发布 sn-ps:https://stackblitz.com/edit/angular-ivy-xzvkjl

我有一个 Angular 应用程序,其服务将共享 isLoading 标志设置为 true,启动 HTTP 请求,然后使用 finalizeisLoading 标志设置回 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

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

【问题讨论】:

    标签: angular rxjs observable order-of-execution


    【解决方案1】:

    关于finalize

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

    考虑一个在它发出任何东西之前被取消订阅的 observable。我希望 finalize 仍会触发,但我不希望将 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 的微任务队列中,所以您会注意到代码运行方式的显着差异。在使用第一个值调用您的 subscribe 之前,isLoading 已经为 false。

    这是因为延迟之前的一切都是同步运行的,并且会在微任务队列运行之前完成。 delay(0) 之后的所有内容都是异步运行的,将在下次 JS 准备好运行微任务队列时完成。


    最小阻力解

    这不是惯用的 RxJS,但它会按照您期望 finalize 在这种情况下的工作方式工作。

    您可以使用点击运算符来捕获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)
          })
        );
      });
    
    }
    

    【讨论】:

    • 感谢您帮助我了解 finalize 的本质!但是,使用您提供的解决方案,我不再获得我以前使用的 finalize 块的好处,即它为成功和错误情况更新 isLoading 值。如果我调整您的解决方案,以便管道还包含具有相同代码的 catchError 清除标志并重新引发错误,我将获得完整的解决方案。与 finalize 相比,它似乎很混乱。我确实想解决代码异味问题,但现在我只专注于将 AngularJS 转换为 Angular 而没有太多逻辑更改。
    • 你也可以在点击操作符中看到错误,不管它的价值是什么(你无法处理它们,但如果你愿意,可以在其他地方做)。我已经更新了我的解决方案来为你做到这一点。
    • 实际上,还有一个快速更新,您可能希望在订阅serviceCall() 时而不是在调用它时设置this.isLoading = true。根据 serviceCall() 稍后的组合方式,您可能会在订阅它之前很长一段时间创建您的 observable(例如,与 concat 运算符一起使用)。 Defer 只需在订阅后调用其工厂函数,因此它可以快速解决该问题。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-02-01
    • 2011-04-05
    • 2020-02-13
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多