简体   繁体   English

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

[英]Combining observables in series & parallel to fetch data from multiple APIs

I am trying to check the validity of a function I have written in Typescript, in congruence with RxJS observables, that fetches some bookings from one service and then for each booking fetches its corresponding location and activity from another service. I am trying to check the validity of a function I have written in Typescript, in congruence with RxJS observables, that fetches some bookings from one service and then for each booking fetches its corresponding location and activity from another service.

I am simply writing this post to verify the validity of what I have written and to ask if there is anything I could have done more efficiently.我写这篇文章只是为了验证我所写内容的有效性,并询问是否有什么我可以做的更有效。

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

I am expecting for this function to return a list of bookings alongside their corresponding location and activity.我期待这个 function 返回一个预订列表以及相应的位置和活动。 However more importantly I want to verify that what I am doing is correct and sensible.但更重要的是,我想验证我所做的是否正确和明智。 Is there anything I could have done differently to make it cleaner/ more human-readable (not nit-picking, please )?有什么我可以做的不同的事情来使它更清晰/更易于阅读(请不要吹毛求疵)?

On a different note, that of performance, I also have a follow-up question with regards to performance.另一方面,关于性能,我还有一个关于性能的后续问题。 Given that a list of bookings has common activities and locations.鉴于预订列表具有共同的活动和位置。 Is there a way to only fetch activities and locations without any duplicate HTTP requests?有没有办法只获取活动和位置而没有任何重复的 HTTP 请求? Is this already handled under the hood by RxJS?这是否已经由 RxJS 在后台处理? Is there anything I could have done to make this function more efficient?我能做些什么来让这个 function 更高效吗?

This is how I would tackle this using RxJS:这就是我使用 RxJS 解决这个问题的方法:

  1. Fetch all the Bookings获取所有Bookings
  2. For Each Booking fetch Location and Activities cuncurrently对于每个预订,同时获取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>


note :注意

  1. Reactive Programming, and more generically declarative approaches, focus on avoiding imperative control flows... You should try to write your pipes without conditions (or any other control flow).反应式编程和更一般的声明性方法,专注于避免命令式控制流......您应该尝试编写没有条件(或任何其他控制流)的管道。 To discard empty bookings you can use the filter operator.要丢弃空预订,您可以使用filter运算符。
  2. Avoid nesting streams because this comes at the cost of readability.避免嵌套流,因为这是以可读性为代价的。
  3. forkJoin also takes a spec object which is very useful (part of the overloads)forkJoin还采用了一个非常有用的规范 object (部分重载)

I'm not sure about the efficiency, but, at least for me, it was a little hard to read我不确定效率,但是,至少对我来说,它有点难以阅读

Here's how I'd do it:这是我的做法:

I used a dummy API, but I think it correlates with your situation我使用了一个虚拟 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.这是一个 StackBlitz 演示。

With this in mind, here is how I'd adapt it to your problem:考虑到这一点,这是我如何适应您的问题的方法:

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM