如何延迟一个史诗直到另一个史诗发出值
How to delay one epic until another has emitted a value
在 redux-observable 中,在执行 API 请求之前,我需要等待另一个史诗完成。使用 combineLatest 不起作用,APP_INITIALIZED 完成后没有任何反应。我也试过 withLatestFrom 但都没有用。
const apiGetRequestEpic = (action$, store) => {
return Observable.combineLatest(
action$.ofType('APP_INITIALIZED'),
action$.ofType('API_GET_REQUEST')
.mergeMap((action) => {
let url = store.getState().apiDomain + action.payload;
return ajax(get(url))
.map(xhr => {
return {type: types.API_RESPONSE, payload: xhr.response};
})
.catch(error => {
return Observable.of({ type: 'API_ERROR', payload: error });
});
})
);
};
combineLatest definition
一种方法是(使用伪名称),当收到初始操作 API_GET_REQUEST
时,您会立即开始监听一个 INITIALIZE_FULFILLED
,这表示初始化(或其他)已完成- -我们稍后会开始。收到后,我们 mergeMap
(或 switchMap
以您的用例为准)进入我们的调用以发出另一个 ajax 请求并执行通常的业务。最后,启动我们正在等待的实际初始化的技巧是在整个链的末尾添加一个 startWith()
——这将发出并调度另一个史诗正在等待的动作。
const initializeEpic = action$ =>
action$.ofType('INITIALIZE')
.switchMap(() =>
someHowInitialize()
.map(() => ({
type: 'INITIALIZE_FULFILLED'
}))
);
const getRequestEpic = (action$, store) =>
action$.ofType('API_GET_REQUEST')
.switchMap(() =>
action$.ofType('INITIALIZE_FULFILLED')
.take(1) // don't listen forever! IMPORTANT!
.switchMap(() => {
let url = store.getState().apiDomain + action.payload;
return ajax(get(url))
.map(xhr => ({
type: types.API_RESPONSE,
payload: xhr.response
}))
.catch(error => Observable.of({
type: 'API_ERROR',
payload: error
}));
})
.startWith({
type: 'INITIALIZE'
})
);
您没有提到一切是如何工作的,所以这只是伪代码,您需要根据您的用例进行修改。
综上所述,如果您只在这个位置调用初始化,您也可以直接将该代码包含在单个史诗本身中,或者只创建一个对其进行抽象的辅助函数。将它们分开 epics 通常意味着您的 UI 代码可以独立触发它们中的任何一个——但为了测试目的将它们分开可能仍然是好的。只有您可以拨打电话。
const getRequestEpic = (action$, store) =>
action$.ofType('API_GET_REQUEST')
.switchMap(() =>
someHowInitialize()
.mergeMap(() => {
let url = store.getState().apiDomain + action.payload;
return ajax(get(url))
.map(xhr => ({
type: types.API_RESPONSE,
payload: xhr.response
}))
.catch(error => Observable.of({
type: 'API_ERROR',
payload: error
}));
})
.startWith({ // dunno if you still need this in your reducers?
type: 'INITIALIZE_FULFILLED'
})
);
我认为您没有按照预期的方式使用 combineLatest
。你的例子应该写成:
const apiGetRequestEpic = (action$, store) => {
return (
Observable.combineLatest(
action$.ofType('APP_INITIALIZED').take(1),
action$.ofType('API_GET_REQUEST')
)
.map(([_, apiGetRequest]) => apiGetRequest)
.mergeMap((action) => {
let url = store.getState().apiDomain + action.payload;
return ajax(get(url))
.map(xhr => {
return {type: types.API_RESPONSE, payload: xhr.response};
})
.catch(error => {
return Observable.of({ type: 'API_ERROR', payload: error });
});
})
);
};
这样,只要 'API_GET_REQUEST'
被发出,这个 combineLatest 就会发出,前提是 'APP_INITIALIZED'
至少被派发过一次,或者,如果没有,等待它被派发。
注意 .take(1)
。没有它,你的组合 observable 也会在任何时候发出 'APP_INITIALIZED'
,很可能不是你想要的。
在 redux-observable 中,在执行 API 请求之前,我需要等待另一个史诗完成。使用 combineLatest 不起作用,APP_INITIALIZED 完成后没有任何反应。我也试过 withLatestFrom 但都没有用。
const apiGetRequestEpic = (action$, store) => {
return Observable.combineLatest(
action$.ofType('APP_INITIALIZED'),
action$.ofType('API_GET_REQUEST')
.mergeMap((action) => {
let url = store.getState().apiDomain + action.payload;
return ajax(get(url))
.map(xhr => {
return {type: types.API_RESPONSE, payload: xhr.response};
})
.catch(error => {
return Observable.of({ type: 'API_ERROR', payload: error });
});
})
);
};
combineLatest definition
一种方法是(使用伪名称),当收到初始操作 API_GET_REQUEST
时,您会立即开始监听一个 INITIALIZE_FULFILLED
,这表示初始化(或其他)已完成- -我们稍后会开始。收到后,我们 mergeMap
(或 switchMap
以您的用例为准)进入我们的调用以发出另一个 ajax 请求并执行通常的业务。最后,启动我们正在等待的实际初始化的技巧是在整个链的末尾添加一个 startWith()
——这将发出并调度另一个史诗正在等待的动作。
const initializeEpic = action$ =>
action$.ofType('INITIALIZE')
.switchMap(() =>
someHowInitialize()
.map(() => ({
type: 'INITIALIZE_FULFILLED'
}))
);
const getRequestEpic = (action$, store) =>
action$.ofType('API_GET_REQUEST')
.switchMap(() =>
action$.ofType('INITIALIZE_FULFILLED')
.take(1) // don't listen forever! IMPORTANT!
.switchMap(() => {
let url = store.getState().apiDomain + action.payload;
return ajax(get(url))
.map(xhr => ({
type: types.API_RESPONSE,
payload: xhr.response
}))
.catch(error => Observable.of({
type: 'API_ERROR',
payload: error
}));
})
.startWith({
type: 'INITIALIZE'
})
);
您没有提到一切是如何工作的,所以这只是伪代码,您需要根据您的用例进行修改。
综上所述,如果您只在这个位置调用初始化,您也可以直接将该代码包含在单个史诗本身中,或者只创建一个对其进行抽象的辅助函数。将它们分开 epics 通常意味着您的 UI 代码可以独立触发它们中的任何一个——但为了测试目的将它们分开可能仍然是好的。只有您可以拨打电话。
const getRequestEpic = (action$, store) =>
action$.ofType('API_GET_REQUEST')
.switchMap(() =>
someHowInitialize()
.mergeMap(() => {
let url = store.getState().apiDomain + action.payload;
return ajax(get(url))
.map(xhr => ({
type: types.API_RESPONSE,
payload: xhr.response
}))
.catch(error => Observable.of({
type: 'API_ERROR',
payload: error
}));
})
.startWith({ // dunno if you still need this in your reducers?
type: 'INITIALIZE_FULFILLED'
})
);
我认为您没有按照预期的方式使用 combineLatest
。你的例子应该写成:
const apiGetRequestEpic = (action$, store) => {
return (
Observable.combineLatest(
action$.ofType('APP_INITIALIZED').take(1),
action$.ofType('API_GET_REQUEST')
)
.map(([_, apiGetRequest]) => apiGetRequest)
.mergeMap((action) => {
let url = store.getState().apiDomain + action.payload;
return ajax(get(url))
.map(xhr => {
return {type: types.API_RESPONSE, payload: xhr.response};
})
.catch(error => {
return Observable.of({ type: 'API_ERROR', payload: error });
});
})
);
};
这样,只要 'API_GET_REQUEST'
被发出,这个 combineLatest 就会发出,前提是 'APP_INITIALIZED'
至少被派发过一次,或者,如果没有,等待它被派发。
注意 .take(1)
。没有它,你的组合 observable 也会在任何时候发出 'APP_INITIALIZED'
,很可能不是你想要的。