简体   繁体   English

RXJS:返回嵌套的 observables

[英]RXJS: returning nested observables

I'm trying to return a UserDetail object which consists of a User and Results, User is retrieved via the accessToken of Account (all retrieved via individual async calls).我试图返回一个由用户和结果组成的 UserDetail 对象,用户是通过帐户的 accessToken 检索的(全部通过单独的异步调用检索)。 At the moment I'm stuck at figuring out how I can return this UserDetail object when I navigate to the detailcomponent (I know it is not possible to literally return the object since the call is async, but I need to use this object in my component).目前,当我导航到 detailcomponent 时,我一直在想如何返回这个 UserDetail 对象(我知道不可能真正返回对象,因为调用是异步的,但我需要在我的成分)。

The code below gives me the following error:下面的代码给了我以下错误:

Type 'Observable' is not assignable to type 'Observable' “Observable”类型不可分配给“Observable”类型

I tried using pipes and maps since I've read this is how I should 'return' async calls to the component calling the function.我尝试使用管道和映射,因为我读过这是我应该如何将异步调用“返回”到调用函数的组件。 The component is then supposed to handle the subscription, but I can't even get that far without creating errors.然后该组件应该处理订阅,但我什至不能在不产生错误的情况下走那么远。


@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);
                })
              );
            }
          })
        );
      })
    );

Try using switchMap instead of map .尝试使用switchMap而不是map map simply transforms one value into another, whereas switchMap allows you to switch to another observable. map只是将一个值转换为另一个值,而switchMap允许您切换到另一个 observable。

Without knowing too much about what's going on, I think you want something like this:在不太了解发生了什么的情况下,我认为您想要这样的东西:

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;
  })
);

Notice how it's no longer indented like yours, but a sequence of pipe operators.注意它不再像你的那样缩进,而是一系列管道操作符。 Whenever you want to switch to a new observable, use switchMap or concatMap .每当您想切换到新的 observable 时,请使用switchMapconcatMap We are only using map to map the result of the last observable to the value that we want to return from the function.我们只使用map将最后一个 observable 的结果map到我们想要从函数返回的值。

Whenever state needs to be saved out of the middle of a pipe, I am using tap to assign it to a variable.每当需要在管道中间保存状态时,我都会使用 tap 将其分配给一个变量。

Also, you are making (possibly) redundant calls to this.rs.getResults(this.auth.token) .此外,您正在(可能)对this.rs.getResults(this.auth.token)冗余调用。 The call doesn't change based on the what the id param is, so you could just fetch once, and read from a cache on subsequent calls.调用不会根据id参数而改变,因此您只需获取一次,然后在后续调用中从缓存中读取。

Edit: concatMap is also an option.编辑: concatMap也是一个选项。 They are slightly different.它们略有不同。 Both are sufficient for the purposes of this answer - I'm not going to make assumptions about your use case.两者都足以满足此答案的目的 - 我不会对您的用例做出假设。

first of all you should avoid nested piping when possible so it is maintainable.首先,您应该尽可能避免嵌套管道,以便它可维护。 Instead combine Observables.而是结合 Observables。

But for your problem: just remove "of(ud)" and then just return ud in your last map.但是对于您的问题:只需删除“of(ud)”,然后在您的最后一张地图中返回 ud 。

Tipp for maintainability:可维护性提示:

I see you have one call wich don't need do be resolved in order.我看到你有一个电话,不需要按顺序解决。


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
                                  }));

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM