Angular2: which is the best way to make multiple sync calls with Observables?

筅森魡賤 提交于 2019-11-30 07:29:11

I can't guarantee that this is the best or most polite way because RxJs is such a powerful library where you can achieve the same result in many different ways, but I'll give it a shot.
I will chip in two options.

Assuming your service looks something like this:

userservice.ts

@Injectable()
export class UserService {

  constructor(private http: Http) { }

  getUserId(): Observable<{ id: number, name: string }> {
    return Observable.of({ id: 3, name: 'Bob' }).delay(300);
    /* 
     * replace with http:
     * return this.http.get('http://my.api.com/api/user/id').map(res => res.json());
     */
  }

  getUserPermission(userId: number): Observable<{ permissions: string[] }> {
    return Observable.of({ permissions: ['user', 'admin'] }).delay(300);
    /* return this.http.get(`http://my.api.com/api/user/${userId}/permissions`).map(res => res.json()); */
  }

  getUserInfo(userId: number): Observable<{ info: string }> {
    return Observable.of({ info: 'is a nice person'}).delay(300);
    /* return this.http.get(`http://my.api.com/api/user/${userId}/info`).map(res => res.json()); */
  }
}

Notice that the methods return Observables of JSON-objects!
Since Angular http already returns Observables, it is probably the easiest and cleanest to keep it an Observable-chain all the way down.
Of course you could use the map-operator (f.e. .map(result => result.info)) inside of the service method to make the return type to Observable<string> instead of Observable<{ info: string }>.


switchMap

This approach is suited for requests that have to happen in a specific order.

this.userService.getUserId()
  .switchMap(userResult =>
    this.userService.getUserPermission(userResult.id)
    .switchMap(permissionsResult =>
      this.userService.getUserInfo(userResult.id)
        .map(infoResult => ({
          id: userResult.id,
          name: userResult.name,
          permissions: permissionsResult.permissions,
          info: infoResult.info
        }))
    )
  )
  .subscribe(v => console.log('switchmap:', v));

If you open the network-tab of your browser you will see that the requests are executed in sequence, meaning that each request has to finish before the next one starts. So getUserId() has to finish before getUserPermission() starts, which in turn has to finish before getUserInfo() can run... and so on.

You can also use mergeMap instead. The only difference is, that switchMap can cancel an ongoing http-request when a new value is emitted by the source observable. Look here for a good comparison.

forkJoin

This approach allows you to execute requests in parallel.

this.userService.getUserId()
  .switchMap(userResult => Observable.forkJoin(
    [
      Observable.of(userResult),
      this.userService.getUserPermission(userResult.id),
      this.userService.getUserInfo(userResult.id)
    ]
  ))
  .map(results => ({
    id: results[0].id,
    name: results[0].name,
    permissions: results[1].permissions,
    info: results[2].info
  }))
  .subscribe(v => console.log('forkJoin:', v));

Since forkJoin runs all the Observable sequences it has been given in parallel, it is the better option if the requests (or at least some of them) don't depend on each other.
In this example, the getUserId()-request will run first, and once it has finished both getUserPermission() and getUserInfo() will start running in parallel.


Both methods will return an object with the following structure:

{
    "id": 3,
    "name": "Bob"
    "permissions": [
        "user",
        "admin"
    ],
    "info": "is a nice person"
}
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!