简体   繁体   English

NGRX 效果 / RXJS:循环 API 调用和操作

[英]NGRX Effects / RXJS: Loop API Calls and Actions

The goal is to loop through an Array of IDs and for each ID make an API Call and dispatch an Action with the result.目标是循环遍历 ID 数组,并为每个 ID 进行 API 调用并使用结果发送一个 Action。

My current idea:我目前的想法:

someEffect$ = createEffect(() => this.actions$.pipe(
    ofType(someAction),
    withLatestFrom(this.store.select(selectIds)),
    switchMap(([_, ids]) => {
      return forkJoin(ids.map(id =>
        this.service.getData(id))).pipe(
        map(result => {
          return actionSuccess({result});
        }),
        catchError((error: HttpErrorResponse) => {
          return of(actionError({error}));
        })
      )
    })
  ));

This kinda works, but has 2 unwanted behaviours:这有点管用,但有两种不良行为:

  1. actionSuccess gets only dispatched once and with the result of all api calls as an array. actionSuccess 仅被调度一次,所有 api 调用的结果作为一个数组。 I want one dispatch for each API Call.我要为每个 API 呼叫发送一次。

Replacing forkJoin with merge triggers an action for each ID, but then the API Calls are no longer happening, so there has to be more to it.将 forkJoin 替换为 merge 会触发每个 ID 的操作,但随后 API 调用不再发生,因此必须有更多。

  1. forkJoin waits for all API Calls to finish and only then continues. forkJoin 等待所有 API 调用完成,然后才继续。 I want to dispatch actionSuccess immediately after every time getData() finishes.我想在每次 getData() 完成后立即发送 actionSuccess。

I know you are asking how to dispatch actions looping through an array in the effects but, how about if, for example, let the component handle the looping operation, with that, you will be dispatching for each id a success action.我知道你在问如何在effects中调度循环遍历数组的动作,但是,例如,如果让组件处理循环操作,那么你将为每个 id 调度一个成功的动作。

Component:成分:

loadCustomerById(): void {
  this.store.select(ClientSelectors.selectIds).subscribe((ids) => {
    if (ids.length) {
      for (const id of ids) {
       this.store.dispatch(ClientActions.loadCustomerById({ payload: id }));
      }
    }
  })
}

Effects:效果:

loadCustomerById$ = createEffect(() => this.actions$.pipe(
    ofType(loadCustomerById),  
    // you already get the id from action
    // no need of withLastestFrom  
    mergeMap(({ payload: id }) => 
      this.service.getData(id).pipe(
        map((response) => ClientActions.loadCustomerByIdSuccess({ payload: response })),
        catchError((error) => of(ClientActions.loadCustomerByIdFail({ error })))
      )
    ) 
  ));

Use merge instead of forkJoin - for more info see https://timdeschryver.dev/snippets#multiple-service-calls-from-an-effect使用 merge 而不是 forkJoin - 有关更多信息,请参阅https://timdeschryver.dev/snippets#multiple-service-calls-from-an-effect

refresh$ = createEffect(() =>
  this.actions$.pipe(
    ofType(CustomerActions.refresh),
    exhaustMap(({ customerIds }) =>
      merge(
        ...ids.map((id) =>
          this.customersService.getCustomer(id).pipe(
            map(CustomerActions.getCustomerSuccess),
            catchError((err) =>
              of(CustomerActions.getCustomerFailed(id, err.message)),
            ),
          ),
        ),
      ),
    ),
  ),
)

I'd suggest converting the Observable of the Array into an Observable which emits all of the elements in the Array using from :我建议将 Array 的 Observable 转换为 Observable,它使用from发出 Array 中的所有元素:

someEffect$ = createEffect(() => this.actions$.pipe(
  ofType(someAction),
  withLatestFrom(this.store.select(selectIds)),
  switchMap(([_, ids]) => from(ids)),
  mergeMap(id => this.service.getData(id).pipe(
    map(result => actionSuccess({ result })),
    catchError(error => of(actionError({ error })))
  ))
));

Depending on the length of ids , however, this could very quickly become untenable, and it might be worth refactoring your backend to accept a batch call.然而,根据ids的长度,这可能很快变得站不住脚,并且可能值得重构您的后端以接受批处理调用。

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

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