Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Make observable to behave like promise in RxJS?

Considering I have a function which calls a server and returns user data through Observable:

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

How do I make it to behave more like a Promise?

The problem is, that the request won't be made until someone subscribes to the returned observable. Also, when multiple subscriptions are done, the server will be called multiple times.

What I want is:

  • To call server as soon as getUserById() is called (the subscription should be optional, only if caller really wants to obtain the returned value or cares to know when request is complete)

  • Multiple subscriptions should not spawn multiple requests, but should return the same value obtained from the server

With this approach I will be able to make this function work:

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

When calling refreshUser() the request should be made and this.currentUser = user should be evaluated. If I want, I can subscribe to the returned observable in order to obtain fresh data, e.g:

// Just refreshes the current user
refreshUser();

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

I've tried to use publish(), refCount() and share() in different combination, but it only helps with the first requirement (i.e. avoiding multiple calls to the server), but how do I make the original observable hot?


Update

After further investigation with different options it seems like solution with making getUserById() hot will not solve it, because I have two mapping functions on different levels (one in getUserById() and one in refreshUser()) and I need both of them to execute. In order to solve this, I need to somehow "push" the observable from bottom to top. And it looks like it will require a lot of boilerplate code for each level.

In promises it will look pretty simple:

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

Is there a simpler solution with RxJS or Promises are actually better suited for the job?

like image 830
Slava Fomin II Avatar asked Jun 22 '17 01:06

Slava Fomin II


1 Answers

From your description it looks like you could do something like this:

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

If you subscribe to the same Observable returned from getUserById multiple times you'll always get the same result.

like image 110
martin Avatar answered Oct 29 '22 12:10

martin