简体   繁体   English

React Actions 使用 RxJS 进行并行异步调用

[英]React Actions using RxJS for parallel async calls

I am having an existing action that calls the api with list of IDs as querystring parameter and fetches the thumbnail response for the passed IDs.我有一个现有的操作,它使用 ID 列表作为查询字符串参数调用 api,并获取传递的 ID 的缩略图响应。 In some cases, count of IDs can be 150-200 records.在某些情况下,ID 计数可以是 150-200 条记录。 Hence, I am working on making this APIs call batch based and run in parallel using forkJoin and subscribe methods of RxJs.因此,我正在使用 RxJs 的forkJoinsubscribe方法使此 API 基于批处理并并行运行。 I'm facing following 3 issues regarding the implementation.关于实施,我面临以下 3 个问题。

  1. I am able to get the batch based response in next method of subscribe.我能够在下一个订阅方法中获得基于批处理的响应。 However, the action invoked in this function along with response is not getting invoked.但是,在此 function 中调用的操作以及响应没有被调用。

  2. maxParallelQueries dont seems to be working as intended. maxParallelQueries 似乎没有按预期工作。 All the fetchThumbnailObservables() are executed at once and not in the batches of 3 as set as constant/所有 fetchThumbnailObservables() 都立即执行,而不是设置为常量/ 3 的批次

  3. How to handle return of observable so that mergeMap do not give syntax error.如何处理 observable 的返回,以便 mergeMap 不会给出语法错误。 Argument of type '({ payload }: IAction) => void' is not assignable to parameter of type '(value: IAction, index: number) => ObservableInput<any>'. Type 'void' is not assignable to type 'ObservableInput<any>'.ts(2345)

Here is my current code looks like.这是我当前的代码的样子。 Any help would be great on this.任何帮助都会很好。

const getThumbnails: Epic<IAction, IAction, IStoreState> = (action$, state$) =>
    action$.ofType(ActionTypes.AssetActions.DATA.FETCH_THUMBNAILS).pipe(
        mergeMap( ({ payload }) => {
            
            const assetIds = Object.keys(payload);
            const meta = payload;

            const batchSize = 10;
            const maxParallelQueries = 3;
            
            const fetchThumbnailObservables : Observable<IThumbnailResponse>[] = [];

            for (let count = 0; count < assetIds.length; count += batchSize) {
                fetchThumbnailObservables.push(AssetDataService.fetchThumbnailData(assetIds.slice(count, count + batchSize)));
            }

             forkJoin(fetchThumbnailObservables)
            .pipe(mergeMap(fetchThumbnailObservable => fetchThumbnailObservable, maxParallelQueries)) // Issue 1 :  maxParallelQueries limit is not working
            .subscribe({ 
                complete: () => { }, 
                error: () => { },
                next: (response) => { 
                     // Issue 2 :  below action dont seems to be getting invoked
                    actions.AssetActions.receivedThumbnailsAction(response, meta) 
                },     
             });
    
            // Issue 3 :  How to handle return of observable 
            // Added below code for prevennting error raised by mergeMap( ({ payload }) => {
            return new Observable<any>();
        }),
        catchError((error) => of(actions.Common.errorOccured({ error })))
    );
    ```

I do not know Epics, but I think I can give you some hints about how to address this problem.我不知道 Epics,但我想我可以给你一些关于如何解决这个问题的提示。

Let's start from the fact that you have an array of ids, assetIds , and you want to make an http call for each id with a controlled level of concurrency, maxParallelQueries .让我们从这样一个事实开始,即您有一个 id 数组assetIds ,并且您想对每个 id 进行 http 调用,并具有可控的并发级别maxParallelQueries

In this case from function and mergeMap operator are yor friends.在这种情况下from function 和mergeMap运算符是你的朋友。

What you need to do is你需要做的是

// from function transform an array of values, in this case, to a stream of values
from(assetIds).pipe(
  mergeMap(id => {
    // do whatever you need to do and eventually return an Observable
    return AssetDataService.fetchThumbnailData(id)
  }, maxParallelQueries) // the second parameter of mergeMap is the concurrency
)

In this way you should be able to invoke the http calls keeping the limit of concurrency you have established通过这种方式,您应该能够调用 http 调用,保持您建立的并发限制

  1. I am able to get the batch based response in next method of subscribe.我能够在下一个订阅方法中获得基于批处理的响应。 However, the action invoked in this function along with response is not getting invoked.但是,在此 function 中调用的操作以及响应没有被调用。

You mustn't call an action imperatively in redux-observable.你不能在 redux-observable 中强制调用一个动作。 Instead you need to queue them.相反,您需要将它们排队。 actions.AssetActions.receivedThumbnailsAction(response, meta) would return an plain object of the shape {type: "MY_ACTION", payload: ...} instead of dispatching an action. actions.AssetActions.receivedThumbnailsAction(response, meta)将返回形状为{type: "MY_ACTION", payload: ...}的普通 object,而不是调度操作。 You rather want to return that object wrapped inside an observable inside mergeMap in order to add the next action to the queue.您宁愿返回 object 包装在 mergeMap 内部的可观察mergeMap中,以便将下一个操作添加到队列中。 (see solution below) (见下面的解决方案)

  1. maxParallelQueries dont seems to be working as intended. maxParallelQueries 似乎没有按预期工作。 All the fetchThumbnailObservables() are executed at once and not in the batches of 3 as set as constant/所有 fetchThumbnailObservables() 都立即执行,而不是设置为常量/ 3 的批次

First guess: What is the return type of AssetDataService.fetchThumbnailData(assetIds.slice(count, count + batchSize)) ?第一个猜测: AssetDataService.fetchThumbnailData(assetIds.slice(count, count + batchSize))的返回类型是什么? If it is of type Promise then maxParallelQueries has no effect because promises are eager and start immediately when created thus before mergeMap has the chance to control the request.如果它是Promise类型,那么maxParallelQueries无效,因为 promise 是急切的,并且在创建时立即启动,因此在mergeMap有机会控制请求之前。 So make sure that fetchThumbnailData returns an observable.所以确保fetchThumbnailData返回一个 observable。 Use defer rather than from if you need to convert a Promise to an Observable to make sure that a promise is not being fired prematurely inside fetchThumbnailData .如果您需要将 Promise 转换为 Observable 以确保 promise 不会在fetchThumbnailData内过早触发,请使用defer而不是from

Second guess: forkJoin is firing all the request so mergeMap doesn't even have the chance to restrict the number of parallel queries.第二个猜测: forkJoin正在触发所有请求,因此mergeMap甚至没有机会限制并行查询的数量。 I'm surprised that typescript doesn't warn you here about the incorrect return type in mergeMap(fetchThumbnailObservable => fetchThumbnailObservable, ...) .我很惊讶 typescript 在这里没有警告您mergeMap(fetchThumbnailObservable => fetchThumbnailObservable, ...)中的返回类型不正确。 My solution below replaces forJoin with from and should enable your maxParallelQueries .我下面的解决方案将forJoin替换为from并且应该启用您的maxParallelQueries

  1. How to handle return of observable so that mergeMap do not give syntax error.如何处理 observable 的返回,以便 mergeMap 不会给出语法错误。 Argument of type '({ payload }: IAction) => void' is not assignable to parameter of type '(value: IAction, index: number) => ObservableInput'. '({ payload }: IAction) => void' 类型的参数不可分配给 '(value: IAction, index: number) => ObservableInput' 类型的参数。 Type 'void' is not assignable to type 'ObservableInput'.ts(2345)类型 'void' 不可分配给类型 'ObservableInput'.ts(2345)

This is not a syntax error.这不是语法错误。 In an epic you have either the option to return an empty observable return empty() if no subsequent actions are intended or dispatch another action by returning an observable of type IAction .在史诗中,如果没有后续操作,您可以选择返回一个空的可观察对象return empty() ,或者通过返回IAction类型的可观察对象来调度另一个操作。 Here my (untested) attempt to meet your requirements:在这里,我(未经测试)尝试满足您的要求:

const getThumbnails: Epic<IAction, IAction, IStoreState> = (action$, state$) =>
  action$.ofType(ActionTypes.AssetActions.DATA.FETCH_THUMBNAILS).pipe(
    mergeMap( ({ payload }) => {
            
      const assetIds = Object.keys(payload);
      const meta = payload;

      const batchSize = 10;
      const maxParallelQueries = 3;
            
      const fetchThumbnailObservables : Observable<IThumbnailResponse>[] = [];

      for (let count = 0; count < assetIds.length; count += batchSize) {
        fetchThumbnailObservables.push(
          AssetDataService.fetchThumbnailData(
             assetIds.slice(count, count + batchSize)
          )
        );
      }

      return from(fetchThumbnailObservables)
        .pipe(
          mergeMap(
            fetchThumbnailObservable => fetchThumbnailObservable,
            maxParallelQueries
          ),
          map((response) =>
            // dispatch action for each response
            actions.AssetActions.receivedThumbnailsAction(response, meta)
          ),
          // catch error early so epic continue on error
          catchError((error) => of(actions.Common.errorOccured({ error })))
        );
    })
  );

Please notice that I moved catchError closer to the request (inside the inner forkJoin Observable) so that the epic does not get terminated when an error occurs.请注意,我将catchError移到了更靠近请求的位置(在内部 forkJoin Observable 内部),以便在发生错误时史诗不会终止。 Error Handling - redux observable This is so because when catchError is active it replaces the failed observable with the one that is returned by catchError . 错误处理 - redux observable这是因为当catchError处于活动状态时,它会用catchError返回的 observable 替换失败的 observable。 And I assume you don't want to replace the whole epic in case of an error?而且我假设您不想在出现错误的情况下替换整个史诗?

And one thing: redux-observable handles the subscriptions for you so won't need to call .subscribe or .unsubscribe anywhere in your application.还有一件事:redux-observable 为您处理订阅,因此不需要在应用程序的任何地方调用.subscribe.unsubscribe You just need to care a about the lifecycle of observables.你只需要关心 observables 的生命周期。 Most important to know here is when does an observable start, what happens to it when it completes, when does it complete or what happens when an observable throws.这里最重要的是要知道 observable 何时开始,完成时会发生什么,何时完成或 observable 抛出时会发生什么。

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

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