【问题标题】:AuthorizeService isAuthenticated() subscribe being called multiple times with rxjs concat使用 rxjs concat 多次调用 AuthorizeService isAuthenticated() subscribe
【发布时间】:2020-08-31 14:31:39
【问题描述】:

我正在使用来自 Visual Studio 模板的默认客户端身份验证服务。

有一个打字稿 AuthorizeService 有一个名为 isAuthenticated 的函数,它调用下面的函数并检查它是否为空。

getUser 函数:

public getUser(): Observable<IUser> {
    return concat(
      this.userSubject.pipe(take(1), filter(u => !!u)),
      this.getUserFromStorage().pipe(filter(u => !!u), tap(u => this.userSubject.next(u))),
      this.userSubject.asObservable());
  }

当 .subscribe 在上述函数上被调用时。订阅被调用了 3 次。大概对于 concat 函数中的每个 observable 。使用上面的 getUser,我希望 subscribe 被调用一次。我将如何实现这一目标?

我尝试将上面的内容转换为返回一个值但由于某种原因没有成功的嵌套承诺,在返回结果后,即使用户存在于会话存储中,它也会返回 resolve(null)

【问题讨论】:

  • 应该 getUser() 真的只发出一次,还是应该在用户更改时返回的 observable 发出当前用户?

标签: angular .net-core rxjs oidc-client


【解决方案1】:

我假设您希望 getUser() 只返回一个用户。现在,您的逻辑是“获取 userSubject 的当前值并从存储中获取用户,并获取 userSubject 的当前值和所有未来值。”

这意味着如果 userSubject 的值是真实的,您将获得至少 3 个用户。这就是你的逻辑。

我不确定您所说的“仅订阅一次”是什么意思,但我假设您的意思是“仅返回 1 个用户”。一种简单的方法是只从你的 concat 调用中获取一个值:

return concat(...).pipe(take(1));

这可能会导致不可预知的行为。三个流中的哪一个首先发出一个值将是您采用的值。如果 getUserFromStorage() 需要一些时间才能完成,您将始终返回 null。我猜这就是你嵌套 Promise 时会发生的事情(尽管我必须查看你的代码才能确定)。

实现这一点的更好方法是使用 switchMap 或 mergeMap(在这种情况下都可以使用)我还猜测如果 userSubject 中没有用户,您只想从后端获取用户。这种方法将有效地缓存当前经过身份验证的用户。

public getUser(): Observable<IUser> {
  return this.userSubject.pipe(
    take(1),
    mergeMap(u => {
      if(u) return of(u);
      return this.getUserFromStorage().pipe(
        tap(u => u && this.userSubject.next(u))
      );
    }), 
    take(1)
  );
}

这是做什么的?只有当来自 userSubject 的用户不真实时,它才会尝试从存储中获取用户。如果 userSubject 中有用户,则永远不会调用(或订阅)this.getUserFromStorage()。值得注意的是,如果 getUserFromStorage() 只返回一个值,则不需要第二次调用 take(1)。这还假设如果存储中没有用户,则 getUserFromStorage() 返回 null。

最后,我删除了所有过滤器,因为(根据您的描述)如果主题中没有用户且存储中没有用户,您似乎希望此流返回 null。如果我们过滤出 null 返回,那么我们将永远不会返回 null。我所做的是,我们只在 getUserFromStorage() 返回 null 时才返回 null。

【讨论】:

  • 我最终在应用启动时初始化了用户,并为后续请求返回 this.userSubject.asObservable()
【解决方案2】:

考虑改用forkJoin(当所有可观察对象完成后,将每个可观察对象的最后一个发出值作为数组发出),如下所示:

注意:我已将 take 运算符添加到最后一个 observable 以便它完成

public getUser(): Observable <[IUser, IUser, IUser]> {
  return forkJoin(
    this.userSubject.pipe(take(1), filter(u => !!u)),
    this.getUserFromStorage().pipe(filter(u => !!u), tap(u => this.userSubject.next(u))),
    this.userSubject.asObservable().pipe(take(1))
  );
}

【讨论】:

  • 感谢您的快速回复。有了上面,函数永远不会解析/订阅/完成
  • userSubject Subject 还是BehvaiorSubject?
  • 看来你只需要getUserFromStorage
  • 是 BehaviorSubject
猜你喜欢
  • 1970-01-01
  • 2018-11-30
  • 1970-01-01
  • 2021-03-22
  • 1970-01-01
  • 1970-01-01
  • 2020-09-14
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多