【问题标题】:Make observable to behave like promise in RxJS?让 observable 在 RxJS 中表现得像 Promise?
【发布时间】:2017-11-25 02:26:48
【问题描述】:

考虑到我有一个调用服务器并通过 Observable 返回用户数据的函数:

getUserById (id: number): Observable<User> {
  return this.http.get('/users/' + id)
    .map(response => response.json())
  ;
}

如何让它表现得更像 Promise?

问题是,在有人订阅返回的 observable 之前不会发出请求。另外,当多个订阅完成时,服务器会被调用多次。

我想要的是:

  • 在调用getUserById() 时立即调用服务器(订阅应该是可选的,仅当调用者确实想获取返回值或想知道请求何时完成时)

  • 多个订阅不应产生多个请求,而应返回从服务器获得的相同值

通过这种方法,我将能够使此功能发挥作用:

refreshUser (): Observable<User> {
  return repository.getUserById(this.currentUser.id)
    .map(user => this.currentUser = user)
  ;
}

当调用refreshUser() 时,应发出请求并评估this.currentUser = user。如果我愿意,我可以订阅返回的 observable 以获得新的数据,例如:

// Just refreshes the current user
refreshUser();

// Refreshes the current user and obtains fresh data
refreshUser().subscribe(user => console.log('Refreshed user', user));

我尝试以不同的组合使用publish()refCount()share(),但它仅有助于满足第一个要求(即避免多次调用服务器),但我如何使原始的 observable热吗?


更新

在使用不同选项进行进一步调查后,使getUserById() 变热的解决方案似乎无法解决它,因为我在不同级别上有两个映射函数(一个在getUserById() 和一个在refreshUser()),我需要两者其中执行。为了解决这个问题,我需要以某种方式将 observable 从底部“推”到顶部。而且看起来每个级别都需要大量样板代码。

在 promises 中看起来很简单:

getUserById (id: number): Promise<User> {
  return this.http.get('/users/' + id)
    .map(response => response.json())
    .toPromise()
  ;
}
refreshUser (): Promise<User> {
  return repository.getUserById(this.currentUser.id)
    .then(user => {
      this.currentUser = user;
      return user;
    })
  ;
}

有没有更简单的 RxJS 或 Promises 解决方案实际上更适合这项工作?

【问题讨论】:

  • 简单但肮脏:在返回 observable 之前,在服务中订阅它。
  • 我不明白。如果你只使用getUserByIdrefreshUser,那应该总是发出另一个请求。为什么要getUserByIdhot?

标签: angular rxjs reactivex


【解决方案1】:

从你的描述看来你可以这样做:

getUserById (id: number): Observable<User> {
  const observable = this.http.get('/users/' + id)
    .map(response => response.json())
    .publishReplay(1)
    .refCount()
    .take(1);

  observable.subscribe();
  return observable;
}

如果您多次订阅从getUserById 返回的同一个 Observable,您将始终得到相同的结果。

【讨论】:

  • “调用 getUserById() 后立即调用服务器”。 refCount 并非如此
  • 它甚至不应该。这不是这里使用它的原因。
【解决方案2】:

看起来您需要使用 a 或 SubjectBehaviorSubject 而不仅仅是普通的 Oberservable。在您的情况下,BehaviorSubject 更好,因为它存储当前值。

声明两个变量,一个是普通的User类型,另一个是UserBehaviorSubject

currentUser:User;
userBSubject:BehaviorSubject<User>;

因为您需要BehaviorSubject 的初始值,您可以在构造函数中对其进行初始化:

constructor(){
  this.userBSubject = new BehaviorSubject<User>(this.currentUser)
}

这就是神奇之处。每次你需要修改(在你的情况下,“刷新”)数据时,你将调用方法.next()

refreshUser() {
    return repository.getUserById(this.currentUser.id)
      .subscribe(user => {
        this.userBSubject.next(user);
        return this.userBSubject.asObservables()
      });
}

那么你可以这样做:

// Just refreshes the current user
refreshUser();

// Refreshes the current user and obtains fresh data
refreshUser().subscribe(user => console.log('Refreshed user', user));

【讨论】:

  • 感谢您的回答!在这一点上,使用其中一个“主题”看起来是唯一可行的选择,但是,我发现它太复杂和麻烦了。我正在努力使我的通信层尽可能纤薄。在花时间试图弄清楚这一点之后,我意识到 Promise 实际上更适合这个问题(并且基于 Promise 的解决方案变得更加精简和通用)。我想我现在会坚持下去。
猜你喜欢
  • 1970-01-01
  • 2016-12-25
  • 2018-02-19
  • 2017-06-14
  • 2018-02-05
  • 2019-05-31
  • 2016-12-11
  • 2017-05-04
  • 2012-05-18
相关资源
最近更新 更多