创建具有多个 API 调用的 Redux Observable Epic - 将第一个的结果传递给第二个
Creating a Redux Observable Epic with multiple API Calls - Passing the results of the first into the second
这不是第一次有人问这样的问题,但出于某种原因,我真的很难理解 Observables 和 RxJs,所以我想我需要澄清一下.
我正在尝试编写一部将执行以下操作的史诗:
- 接收一个字符串
- 根据该字符串进行 API 调用
- 根据第一个的结果再进行三个(独立的)API 调用,并将它们写入状态。
我读过 concatMap
和 forkJoin
,它们似乎很有用,因为 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)
这不是第一次有人问这样的问题,但出于某种原因,我真的很难理解 Observables 和 RxJs,所以我想我需要澄清一下.
我正在尝试编写一部将执行以下操作的史诗:
- 接收一个字符串
- 根据该字符串进行 API 调用
- 根据第一个的结果再进行三个(独立的)API 调用,并将它们写入状态。
我读过 concatMap
和 forkJoin
,它们似乎很有用,因为 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)