在注销 Web 应用程序的过程中,如何防止 API 调用分钟计时器循环?

How to prevent API calls on a minute timer loop when in the process of logging out of a web app?

在我的 Ionic/Angular 应用程序中,我有一个 60 秒的可观察计时器,它只发出与服务器时间同步的当前时间。每分钟我都会获取权限、设置等。我会为每个请求传递一个令牌。注销时我撤销了令牌。这是我的逻辑示例。

旁注:还有一个功能,用户可以“更改登录类型”,例如,他们可以“成为”管理员,这个过程也可能触发类似的情况。

this.clientTimeSub = this.timeService.clientTime
      .pipe(takeUntil(this.logoutService.isLoggingOut$))
      .subscribe(async (latestClientTime) => {
          this.clientTime = { ...latestClientTime };
          // if client time just rolled over to a new minute, update settings
          if (
            this.clientTime?.time?.length === 7 &&
            this.clientTime?.time?.slice(-1) === '0'
          ) {
            await updateSettings();
            await updatePermissions();
            // etc

            // These functions will:
            // (1) make an api call (using the login token!)
            // (2) update app state
            // (3) save to app storage

          }
      });

当我退出应用程序时,有一小段时间 window 我可能正在发送多个 api 请求并且令牌不再有效,因为就在我注销或接近注销时,计时器滚动到新的一分钟。然后我在注销过程中收到 401:未经授权。

我天真的解决方案是在 Subject 或 BehaviorSubject 触发一个值告诉这个 observable 它正在注销时告诉这个 observable 停止传播,你可以在这里看到这个 .pipe(takeUntil(this.logoutService.isLoggingOut$)).

然后,在我的任何注销方法中,我会使用:

logout() {
  this.isLoggingOut.next(true);
  
  ...
  // Logout logic here, token becomes invalidated somewhere here
  // then token is deleted from state, etc, navigate back to login...
  ...
  
 this.isLoggingOut.next(false);
}

在注销的那一小段时间 window 中,客户端计时器应该停止触发并检查它是否滚动到新的一分钟,以防止任何进一步的 api 调用可能未经身份验证。

有什么方法可以轻松防止此问题发生,或者我的逻辑是否存在缺陷可能导致此问题?

感谢任何帮助,谢谢!

首先,将 async-await 与 RXJS 一起使用并不是最好的方法。这是因为 RXJS 作为函数式编程的一种反应方式,具有其“可管道化”的运算符,因此您可以有点“链接”一切。

因此,与其在订阅回调函数中使用计算时间的逻辑,不如使用 filter() RXJ 运算符,而不是使用 await-async 你可以使用 switchMap 运算符并在其中使用 forkJoin 或 concat 运算符。

this.timeService.clientTime
  .pipe(
    // Filter stream (according to your calculation)
    filter((time) => {
      // here is your logic to calculate if time has passed or whatever else you are doing
      // const isValid = ...
      return isValid;
    }),
    // Switch to another stream so you can call api calls
    // Here with "from" we are converting promises to observables in order to be able to use magic of RXJS
    switchMap(_ => forkJoin([from(updateSettings), from(updatePermissions)])),
    // Take until your logout
    takeUntil(this.logoutService.isLoggingOut$)
  ).subcribe(([updateSettings, updatePermissions]) => {
    
   // Basically your promises should just call API services, and other logic should be here
    
  // Here you can use
  // (2) update app state
  // (3) save to app storage
})

如果你像我的例子那样拆分动作,在你的承诺中你只需调用 api 调用来更新你正在做的任何事情,然后当它完成时,在订阅回调中你可以更新应用程序状态,保存到应用程序存储等。所以你可以在这里有 2 个场景:

  1. Api 来自承诺的调用仍在进行中。如果您同时触发注销 takeUntil 将执行此操作,您将不会更新应用程序状态等。
  2. 如果来自 promises 的两个 Api 调用都已完成,则您处于订阅回调块中,如果它只是一个同步代码(希望如此),它将完成。然后可以执行异步代码(您的计时器现在可以发出下一个值,这都是关于 javascript 中的事件循环)