【问题标题】:RXJS: returning nested observablesRXJS:返回嵌套的 observables
【发布时间】:2020-02-27 15:03:59
【问题描述】:

我正在尝试返回一个由用户和结果组成的 UserDetail 对象,用户是通过 Account 的 accessToken 检索的(全部通过单独的异步调用检索)。目前,当我导航到 detailcomponent 时,我一直在弄清楚如何返回这个 UserDetail 对象(我知道由于调用是异步的,因此无法逐字返回该对象,但我需要在我的组件)。

下面的代码给了我以下错误:

类型 'Observable' 不可分配给类型 “可观察”

我尝试使用管道和映射,因为我读过这是我应该如何将异步调用“返回”到调用函数的组件。然后该组件应该处理订阅,但如果不产生错误,我什至无法做到这一点。


@Injectable({
  providedIn: 'root'
})
export class UserResolver implements Resolve<UserDetails> {
  constructor(
    private as: AccountService,
    private rs: ResultService,
    private auth: AuthService
  ) {}

  resolve(
    route: ActivatedRouteSnapshot,
    state: RouterStateSnapshot
  ): Observable<UserDetails> {
    return this.as.getAccount(route.params['id']).pipe(
      map((acc: Account) => {
        this.as.getUserDetails(acc.access_token).pipe(
          map((user: User) => {
            if (user != null) {
              this.rs.getResults(this.auth.token).pipe(
                map((res: Result[]) => {
                  const ud: UserDetails = {
                    user,
                    results: res.filter(x => x.userId === acc.uid)
                  };
                  return of(ud);
                })
              );
            }
          })
        );
      })
    );

【问题讨论】:

    标签: angular asynchronous rxjs


    【解决方案1】:

    尝试使用switchMap 而不是mapmap 只是将一个值转换为另一个值,而switchMap 允许您切换到另一个 observable。

    在不太了解发生了什么的情况下,我认为您想要这样的东西:

    let user: User;
    let account: Account;
    return this.as.getAccount(route.params['id']).pipe(
      tap((acc: Account) => account = account),
      switchMap((acc: Account) => this.as.getUserDetails(acc.access_token)),
      // this filter will stop the next steps from running if the user is null
      // it will also mean a value isn't emitted
      // if you want to emit a null, you will need to modify the pipe
      filter((u: User) => u !== null),
      tap((u: User) => user = u),
      switchMap((u: User) => this.rs.getResults(this.auth.token))
      map((res: Result[]) => {
        const ud: UserDetails = {
          user,
          results: res.filter(x => x.userId === account.uid)
        };
        return ud;
      })
    );
    

    注意它不再像你的那样缩进,而是一系列管道运算符。每当您想切换到新的 observable 时,请使用 switchMapconcatMap。我们只是使用map 将最后一个 observable 的结果映射到我们想要从函数返回的值。

    每当需要从管道中间保存状态时,我都会使用 tap 将其分配给变量。

    此外,您正在(可能)对this.rs.getResults(this.auth.token) 进行冗余调用。调用不会根据 id 参数而改变,因此您只需获取一次,然后在后续调用中从缓存中读取。

    编辑:concatMap 也是一个选项。它们略有不同。对于此答案而言,两者都足够了-我不会对您的用例做出假设。

    【讨论】:

    • OP 必须使用 concatMap 而不是 switchMap,因为嵌套的 observable 依赖于外部 observable 的订阅值。
    • 请告诉我为什么switchMap 在这里不起作用。在 stackblitz 演示中,最好是
    • switchMap 将在这里为您提供预期的结果,但它可能会导致对 OP 的 switchMap 的错误概念。 switchMap 通常用于当您想要取消之前正在进行的 observable 并在有新值可用时订阅新的 observable 时使用。在这种情况下,不需要这样做,简单的concatMap 就可以解决问题。
    • 那么switchMap 会在这里工作吗?好的。它在功能上等同于concatMap吗?是的。我在我的问题中说“switchMap 允许您切换到另一个 observable” - 我没有说这是唯一方式。
    • 我的错,我不是那个意思。正如你所说,switchMap 在这种情况下有效。
    【解决方案2】:

    首先,您应该尽可能避免使用嵌套管道,以使其易于维护。 而是结合 Observables。

    但是对于您的问题:只需删除“of(ud)”,然后在最后一张地图中返回 ud。

    可维护性提示:

    我看到你有一个电话,不需要按顺序解决。

    
    const userDetails$: Observable<Userdetails> = this.rs.getResults(this.auth.token)
                                             .pipe(map((res: Result[]) => {
                                                 const ud: UserDetails = {
                                                 user,
                                                 results: res.filter(x => x.userId === acc.uid)
                                                 };
                                                  return ud;
                                                 });
    
    const userCheck$: Observable<User> = this.as.getAccount(route.params['id'])
                                         .pipe(map((acc: Account) => 
                                         this.as.getUserDetails(acc.access_token));
    
    // Only when both values are available those observables will be ziped. 
    // Only one subscription is needed on hte returned Obersvable 
    
    return zip(userDetails$,userCheck$).pipe(map(([userDetails,user])) => {
                                       if (user != null) {
                                          return userDetails
                                      }else{
                                          // should probably emit a error
                                      }));
    
    

    【讨论】:

    • 我需要来自 userCheck$ 的用户来创建 userDetails 思想,但我认为逻辑保持不变,我应该稍微移动一下代码,对吧?
    猜你喜欢
    • 2017-08-10
    • 2017-03-20
    • 1970-01-01
    • 1970-01-01
    • 2020-04-22
    • 1970-01-01
    • 2023-03-23
    • 1970-01-01
    • 2021-05-10
    相关资源
    最近更新 更多