Angular2:哪个是使用 Observables 进行多个同步调用的最佳方式?

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

在使用 Promises 进行服务调用之前,我正在研究 Angular2/4 中的 Observables。

我想知道进行多个同步调用的最佳方式是什么。 让我用一个例子来解释: 我的应用程序组件有一个 getUserInfo() 方法,需要对 3 个相互依赖的不同服务进行 3 次调用。

getUserId():Number // return the logged in user userId from the session
getUserPermissions(userId):Array<String> // return a list of permission from the userId
getUserInfo(userId):String // return some additional info for the user

现在假设我有一个用户对象如下:

export class User {
id: number;
name: string;
info: string;
permissions: array<string>;
}

我需要用 3 个服务调用的结果创建一个新的 User class 实例,所以我需要 运行:

  1. getUserId();
  2. getUserPermissions();
  3. getUserInfo();

通过 Observable 实现这一目标的最佳和最礼貌的方式是什么?

有了承诺,我会有这样的事情:

this._service.getUserId().then(r1 => {
  let userId: number = r1;
  this._service.getUserPermissions(userId).then(r2 => {
    let userPermissions: Array<String> = r2;
    this._service.getUserInfo(userId).then(r3 => {
      let userInfo: string = r3;
      let user: User = new User(userId, userInfo, userPermissions);
    });
  })
});

我不能保证这是最好的最礼貌的方式,因为RxJs是如此强大的库,您可以在其中实现以许多不同的方式得到相同的结果,但我会试一试。
我将提供两个选项。

假设您的服务如下所示:

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()); */
  }
}

注意方法 return Observables of JSON-objects!
由于 Angular http 已经 returns Observables,它可能是最简单和最干净的保持它一直向下的 Observable-chain。
当然,您可以在服务方法内部使用 map-运算符 (f.e..map(result => result.info)) 将 return 类型设为 Observable<string> 而不是 Observable<{ info: string }>.


switchMap

这种方法适用于必须按特定顺序发生的请求。

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

如果您打开浏览器的网络选项卡,您会看到请求是按顺序执行的,这意味着每个请求都必须在下一个请求开始之前完成。所以 getUserId() 必须在 getUserPermission() 开始之前完成,而 getUserPermission() 又必须在 getUserInfo() 可以 运行 之前完成......等等。

您也可以使用 mergeMap。唯一的区别是,当源可观察对象发出新值时,switchMap 可以取消正在进行的 http 请求。查看 here 进行比较。

forkJoin

这种方法允许您并行执行请求。

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

因为forkJoin 运行所有的 Observable 序列都是并行给出的,如果请求(或至少其中一些)不依赖于每个请求,这是更好的选择其他.
在此示例中,getUserId()-请求将首先 运行,一旦完成,getUserPermission()getUserInfo() 将并行开始 运行ning。


这两种方法都会return一个具有以下结构的对象:

{
    "id": 3,
    "name": "Bob"
    "permissions": [
        "user",
        "admin"
    ],
    "info": "is a nice person"
}