串联和并联组合可观察对象以从多个API提取数据

时间:2019-11-02 09:48:32

标签: javascript rxjs angular2-observables rxjs-pipeable-operators rxjs-observables

我正在尝试检查我用Typescript编写的,与RxJS observables一致的函数的有效性,该函数从一个服务获取某些预订,然后为每个预订从另一服务获取其对应的位置和活动。

我只是写这篇文章来验证我写的内容的有效性,并询问我是否可以做得更有效。

let params = new HttpParams();
params = params.append('status', 'C');
params = params.append('offset', offset.toString());
params = params.append('limit', limit.toString());
return this.http.get(`${this.environment.booking.url}/my/bookings`, { params }).pipe(
    mergeMap((bookings: Booking[]) => {
        if(bookings.length > 0) {
            return forkJoin(
                bookings.map((booking: Booking) =>
                    forkJoin(
                        of(booking),
                        this.activityService.getActivity(booking.activity),
                  this.locationService.getLocation(booking.finalLocation),
                    ).pipe(
                        map((data: [ Booking, Activity, Location ]) => {
                            let booking = data[0];
                            booking.activityData = data[1];
                            booking.finalLocationData = data[2];
                            return booking;
                        })
                    )
                )
            )
        }

        return of([]);
    }),
    catchError((err: HttpErrorResponse) => throwError(err))
);

我希望该功能返回预订清单以及相应的位置和活动。但是,更重要的是,我想验证我所做的事情是正确且明智的。我可以做些什么使它更干净/更易读(不是挑剔,请?)?

另一方面,关于绩效,我还有一个关于绩效的后续问题。鉴于预订清单具有共同的活动和位置。有没有一种方法可以仅获取活动和位置而没有任何重复的HTTP请求? RxJS已经在后台处理了吗?我可以做些什么使该功能更有效吗?

2 个答案:

答案 0 :(得分:0)

这是我使用RxJS解决此问题的方式:

  1. 获取所有Bookings
  2. 对于每个预订,当前分别获取LocationActivities

const { from, of, forkJoin, identity } = rxjs;
const { mergeMap, tap, catchError } = rxjs.operators;

const api = 'https://jsonplaceholder.typicode.com';

const endpoints = {
  bookings: () => `${api}/posts`,
  locations: (id) => `${api}/posts/${id}/comments`,
  activities: (id) => `${api}/users/${id}`
};

const fetch$ = link => from(fetch(link)).pipe(
  mergeMap(res => res.json()),
  catchError(() => from([])),
);

fetch$(endpoints.bookings()).pipe(
  mergeMap(identity),
  mergeMap(booking => forkJoin({
    booking: of(booking),
    locations: fetch$(endpoints.locations(booking.id)),
    activities: fetch$(endpoints.activities(booking.userId)),
  })),
).subscribe(console.log);
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/6.5.3/rxjs.umd.js" integrity="sha256-Nihli32xEO2dsnrW29M+krVxoeDblkRBTkk5ZLQJ6O8=" crossorigin="anonymous"></script>


注释

  1. 响应式编程以及更一般的声明式方法,着重于避免命令式控制流...您应尝试在没有条件(或任何其他控制流)的情况下编写管道。要放弃空预订,可以使用filter运算符。
  2. 避免嵌套流,因为这会牺牲可读性。
  3. forkJoin还带有一个spec对象,该对象非常有用(部分重载)

答案 1 :(得分:0)

我不确定效率,但至少对我而言,这有点难以理解

这就是我要做的:

我使用了虚拟API,但我认为它与您的情况有关

const usersUrl = 'https://jsonplaceholder.typicode.com/users';
const todosUrl = 'https://jsonplaceholder.typicode.com/todos';
const userIds$ = of([1, 2, 3]); // Bookings' equivalent

userIds$
  .pipe(
    filter(ids => ids.length !== 0),
    // Flatten the array so we can avoid another nesting level
    mergeMap(ids => from(ids)),
    // `concatMap` - the order matters!
    concatMap(
      id => forkJoin(ajax(`${usersUrl}/${id}`), ajax(`${todosUrl}/${id}`))
        .pipe(
          map(([user, todo]) => ({ id, user: user.response, todo: todo.response }))
        )
    ),
   toArray()
  )
  .subscribe(console.log)

Here is a StackBlitz demo.

考虑到这一点,以下是我如何将其适应您的问题:

this.http.get(`${this.environment.booking.url}/my/bookings`, { params }).pipe(
    filter(bookings => bookings.length !== 0),
    // Get each booking individually
    mergeMap(bookings => from(bookings)),
    concatMap(
        b => forkJoin(
            this.activityService.getActivity(b.activity),
            this.locationService.getLocation(b.finalLocation),
        )
        .pipe(
            map(([activity, location]) => ({ ...b, activity, location }))
        )
    ),
    // Getting the bookings array again
    toArray()
    catchError((err: HttpErrorResponse) => throwError(err))
);