创建具有多个 API 调用的 Redux Observable Epic - 将第一个的结果传递给第二个

Creating a Redux Observable Epic with multiple API Calls - Passing the results of the first into the second

这不是第一次有人问这样的问题,但出于某种原因,我真的很难理解 Observables 和 RxJs,所以我想我需要澄清一下.

我正在尝试编写一部将执行以下操作的史诗:

  1. 接收一个字符串
  2. 根据该字符串进行 API 调用
  3. 根据第一个的结果再进行三个(独立的)API 调用,并将它们写入状态。

我读过 concatMapforkJoin,它们似乎很有用,因为 concatMap 应该允许我连续执行多个操作,并且可以使用 forkJoin用于将最后 3 API 次调用的结果作为单个数组返回。

这里有一些代码可以尝试让您了解我想要什么。对重复的问题表示歉意,但我将不胜感激。

export const apiFetchEpic = (
  action$: any,
  state$: any,
  { apiClient }: IDependencies,
) =>
  action$.pipe(
    ofType(API_FETCH),
    debounce(() => timer(300)),
    withLatestFrom(state$),
    mergeMap(
      ([
        {
          payload: { config, uri },
        },
      ]: [{ payload: IApiFetchActionProps }, any]) => {
        return from(apiClient.get(uri, config)).pipe(
          mergeMap(res =>
            of(
              apiSetAction(res), // <-- I need the results of this to be passed into the next calls
              apiCompleteAction({ uri }),
            ),
          ),
          catchError(error =>
            of(apiCompleteAction({ uri }), apiErrorAction(error)),
          ),
        )
      },
    ),
  )

我是这样解决的:

import { ofType, combineEpics } from 'redux-observable'
import {
  mergeMap,
  withLatestFrom,
  catchError,
  debounce,
  switchMap,
} from 'rxjs/operators'
import { from, of, timer, forkJoin } from 'rxjs'
import { IDependencies } from '../../configure-store'
import { apiErrorAction } from '../../reducers/errors/actions'
import {
  TOC_FETCH,
  tocSetAction,
  ITocFetchActionProps,
  tocCompleteAction,
} from '../../reducers/table-of-contents/actions'
import { defaults } from 'lodash'

// Can probably use forkJoin here, and create multiple API calls as a parallel observable
// https://github.com/patricktran/react-redux-observable-epic-forkjoin-apis/blob/master/src/epics/index.js

export const tocFetchEpic = (
  action$: any,
  state$: any,
  { apiClient }: IDependencies,
) => {
  return action$.pipe(
    ofType(TOC_FETCH),
    debounce(() => timer(300)), // anti-DDoS
    withLatestFrom(state$),
    mergeMap(
      ([
        {
          payload: { instance },
        },
      ]: [{ payload: ITocFetchActionProps }, any]) => {

        const params = { 'myParam': instance, limit: 1 }
        return from(apiClient.get('endpoint', { params })).pipe(
          mergeMap(
            (result): any => {

              const def = {
                limit: -1,
                'newParam': result.data.results[0],
              }
              const configurations = [
                {
                  uri: 'endpointOne',
                  config: defaults({}, def),
                },
                {
                  uri: 'endpointTwo',
                  config: defaults(
                    {
                      'specialParam': 'table',
                      'specialParamTwo': 'http://example.com',
                      'iso2:lang': 'en',
                    },
                    def,
                  ),
                },
                {
                  uri: 'endpointThree',
                  config: defaults(
                    {
                      'anotherSpecialParam': 'role',
                    },
                    def,
                  ),
                }, 
              ]

              // Create a bunch of observables
              const parallelObservables = configurations.map(api =>
                from(
                  apiClient.get(api.uri, { params: api.config }),
                ).pipe(
                  switchMap(response => of(response)),
                  catchError(err =>
                    of(apiErrorAction(err), tocCompleteAction({ instance })),
                  ),
                ),
              )

              // Return a forkJoin of them.
              return forkJoin(
                parallelObservables,
                (names: any, schoolNames: any, storeNames: any) => {
                  //projection
                  return { // <- The object returned here is ultimately what gets written into state
                    instance: result.data.results[0],
                    tables: names.data.results,
                    labels: [
                      ...schoolNames.data.results,
                      ...storeNames.data.results,
                    ],
                  }
                },
              ).pipe(
                switchMap(response => {
                  return of(
                    tocSetAction(response),
                    tocCompleteAction({ instance }),
                  )
                }),
              )
            },
          ),
          catchError(error =>
            of(tocCompleteAction({ instance }), apiErrorAction(error)),
          ),
        )
      },
    ),
  )
}

export default combineEpics(tocFetchEpic)