尝试使用RXJS制作动态列表时遇到问题

时间:2019-09-17 21:02:58

标签: angular typescript api rxjs observable

我正在尝试创建一个服务工作者,该服务工作者将从API提供有关用户的信息。

我的想法是我不想发出无用的请求,例如两次询问同一位用户,因此我正在尝试实现一个函数,该函数检查我们是否存储了用户数据,以及是否从服务器获取了该数据。

我尝试使用for i in range(0, 10, 2, 4, 5, 6, 7, 8): mapToshare等管道

shareReplay
users = new BehaviorSubject<Array<User>>([]);

所以当我用相同的ID同时调用getUser 2次时,它应该只请求一次。

3 个答案:

答案 0 :(得分:0)

要为行为主体中的数组添加一个值,而又不使该数组的内容发生突变,请创建一个新数组,然后使行为主体发出该新数组。

this.users.next([..this.users.value, user]);

要使用值创建新的可观察值,只需使用

of(user);

您的api为什么返回承诺? http服务返回可观测值,因此您应该能够只返回映射到用户对象的http。

return this.api.get('users/' + id, this.userService.getAuthToken(), ).pipe(
  map(functionToMapResponseToAUserObject),
  tap(sideEfectFunctionToInsertNewUserIntoBehaviorSubject)
)

因此,如果您修改api以返回可观察的结果而不是应许的结果,则可能是您的服务

getUserInfo(id: string): Observable<User> {
  const users = this.users.getValue();
  const user = users.find((u) => u._id === id);
  return user
    ? of(user)
    : this.api.get('users/' + id, this.userService.getAuthToken(), ).pipe(
      map(response => new User(response)),
      tap(user => { this.users.next([...users, user]); })
    )
}

如果您无法修改api,请使用from将诺言转换为可观察的

from(this.api.get('users/' + id, this.userService.getAuthToken(), ))

要停止对同一id的多个请求,您需要一个对象来跟踪正在加载的请求

loading: { [key]: Observable<User> } = {};

将共享重播添加到api调用中,这样就不会重复api调用并将其粘贴到加载缓存中

 let loaded = false;
 const user$ = this.api.get('users/' + id, this.userService.getAuthToken(), ).pipe(
    map(response => new User(response)),
    tap(user => {
      this.users.next([...users, user]);
      if (loading[id]) {
        delete loading[id];
      }
      loaded = true;
    }),
    shareReplay()
 );

 if (!loaded) {
   loading[id] = user$;
 }

然后您可以在触发api调用之前查看是否正在加载,并从加载缓存中返回可观察到的内容。

答案 1 :(得分:0)

阿德里安的答案很接近,但不能完全回答问题的一部分:

  

所以当我用相同的ID同时调用getUser 2次时,它应该只请求一次。

Adrian的解决方案不会执行此操作,因为如果请求仍未解决,则该用户将不会被存储,您最终将两次请求该用户。

通常建议的方法是将缓存存储为可观察对象的映射。

伪打字稿,语法可能是错误的,我还将假设您可以将api调用变成可观察的,如果没有,请寻找Adrian关于如何处理它的答案:

users: Map<string,Observable<User>> = new Map();


getUserInfo(id: string): Observable<User> {
  if(!this.users.get(id)){
     const userObservable = this.api.get('users/' + id, this.userService.getAuthToken(), ) // Or turn the promise to observable with `from`
       .pipe(
         map(response => new User(response)),
         share(),
       );
     this.users.set(id, userObservable) 
  }

  return this.users.get(id)

}

答案 2 :(得分:0)

请尝试这种方法,我相信它将满足您的需求

usersSubject = new Subject<User>();
usersCache = this.usersSubject.pipe(
  scan<Record<string, User>>((cache, user) => {
    cache[user.id] = user;
    return cache;
  }, {}),
  shareReplay(1) // keeping the last version of the cache
);


getUserInfo(id: string): Observable<User> {
  return this.usersCache.pipe(
    pluck(id),
    switchMap(user => iif(() => !user,
      this.api.get('users/' + id, this.userService.getAuthToken()).pipe(
        tap(user => this.usersSubject.next(user)) // caching the user
      ),
      of(user)
    ),
    first() // auto unsubscription, because usersCache will keep pushing values
  )
}

更新: 确保保持订阅为打开状态以保持缓存完整(我想这是一项服务,因此请在构造函数中进行操作):

this.usersCache.subscribe()