Javascript 在redux observable中创作和排序多部史诗
我有一个我不知道如何解决的问题 我有两个Epic可以请求api并更新存储:Javascript 在redux observable中创作和排序多部史诗,javascript,redux,rxjs,redux-observable,Javascript,Redux,Rxjs,Redux Observable,我有一个我不知道如何解决的问题 我有两个Epic可以请求api并更新存储: const mapSuccess = actionType => response => ({ type: actionType + SUCCESS, payload: response.response, }); const mapFailure = actionType => error => Observable.of({ type: actionType + FAILURE,
const mapSuccess = actionType => response => ({
type: actionType + SUCCESS,
payload: response.response,
});
const mapFailure = actionType => error => Observable.of({
type: actionType + FAILURE,
error,
});
const characterEpic = (action$, store) =>
action$.ofType(GET_CHARACTER)
.mergeMap(({ id }) => {
return ajax(api.fetchCharacter(id))
.map(mapSuccess(GET_CHARACTER))
.catch(mapFailure(GET_CHARACTER));
});
const planetsEpic = (action$, store) =>
action$.ofType(GET_PLANET)
.mergeMap(({ id }) => {
return ajax(api.fetchPlanet(id))
.map(mapSuccess(GET_PLANET))
.catch(mapFailure(GET_PLANET));
});
现在我有一个简单的场景,我想创建第三个动作,它结合了上面两个动作,我们称之为fetchcharacterandplanetpic
。我怎么做?
我认为,在许多情况下(在我看来),在第二次操作开始之前将第一次操作的结果发送到存储区是很重要的。这对于承诺和redux thunk来说可能是微不足道的,但是我想不出一种方法来使用rxjs和redux observable
谢谢 我在这方面找到了帮助
首先,我必须创建帮助器方法,使我能够将epics组合在一起:
import { ActionsObservable } from 'redux-observable';
const forkEpic = (epicFactory, store, ...actions) => {
const actions$ = ActionsObservable.of(...actions);
return epicFactory(actions$, store);
};
export const getCharacterAndPlanet = (characterId, planetId) => ({
type: GET_CHARACTER_AND_PLANET,
characterId,
planetId,
});
const fetchCharacterAndPlanetEpic = (action$, store) =>
action$.ofType(GET_CHARACTER_AND_PLANET)
.mergeMap(({ characterId, planetId }) =>
forkEpic(characterEpic, store, getCharacter(characterId))
.mergeMap((action) => {
if (action.type.endsWith(SUCCESS)) {
return forkEpic(planetsEpic, store, getPlanet(planetId))
.startWith(action);
}
return Observable.of(action);
})
);
这让我可以调用任何带有存根的动作的史诗,如:
const getCharacter = id => ({ type: GET_CHARACTER, id });
forkEpic(getCharacterEpic, store, getCharacter(characterId))
…并将返回该史诗的可观察结果。这样我就可以将两部史诗结合在一起:
import { ActionsObservable } from 'redux-observable';
const forkEpic = (epicFactory, store, ...actions) => {
const actions$ = ActionsObservable.of(...actions);
return epicFactory(actions$, store);
};
export const getCharacterAndPlanet = (characterId, planetId) => ({
type: GET_CHARACTER_AND_PLANET,
characterId,
planetId,
});
const fetchCharacterAndPlanetEpic = (action$, store) =>
action$.ofType(GET_CHARACTER_AND_PLANET)
.mergeMap(({ characterId, planetId }) =>
forkEpic(characterEpic, store, getCharacter(characterId))
.mergeMap((action) => {
if (action.type.endsWith(SUCCESS)) {
return forkEpic(planetsEpic, store, getPlanet(planetId))
.startWith(action);
}
return Observable.of(action);
})
);
在本例中,仅当第一个请求以成功结束时,才会调用第二个请求。Tomasz的回答有效,并且有其优点和缺点(最初是在中建议的)。一个潜在的问题是,它使测试更加困难,但并非不可能。e、 您可能必须使用依赖注入来注入forked epic的模拟 在看到他的答案之前,我已经开始打字了,所以我想我还是把它发给后代吧,以防有人对它感兴趣 我之前还回答了另一个非常类似的问题,可能会有所帮助:
我们可以发出
getCharacter()
,然后等待匹配的getCharacter\u成功
,然后再发出getPlanet()
这种方法的一个潜在负面影响是,理论上,这部史诗接收到的GET\u CHARACTER\u SUCCESS
可能与我们所期待的完全不同。filteraction.payload.id===characterId
检查主要保护您不受此影响,因为如果它具有相同的id,那么它是否属于您可能并不重要
要真正解决这个问题,您需要某种独特的事务跟踪。我个人使用一个自定义解决方案,该解决方案涉及使用助手函数来包含唯一的事务ID。类似于以下内容:
let transactionID = 0;
const pend = action => ({
...action,
meta: {
transaction: {
type: BEGIN,
id: `${++transactionID}`
}
}
});
const fulfill = (action, payload) => ({
type: action.type + '_FULFILLED',
payload,
meta: {
transaction: {
type: COMMIT,
id: action.meta.transaction.id
}
}
});
const selectTransaction = action => action.meta.transaction;
然后它们可以这样使用:
const getCharacter = id => pend({ type: GET_CHARACTER, id });
const characterEpic = (action$, store) =>
action$.ofType(GET_CHARACTER)
.mergeMap(action => {
return ajax(api.fetchCharacter(action.id))
.map(response => fulfill(action, payload))
.catch(e => Observable.of(reject(action, e)));
});
const fetchCharacterAndPlanetEpic = (action$, store) =>
action$.ofType(GET_CHARACTER_AND_PLANET)
.mergeMap(action =>
action$.ofType(GET_CHARACTER_FULFILLED)
.filter(responseAction => selectTransaction(action).id === selectTransaction(responseAction).id)
.take(1)
.mapTo(getPlanet(action.planetId))
.startWith(getCharacter(action.characterId))
);
关键的细节是初始的“pend”操作在元对象中持有一个唯一的事务ID。因此,初始操作基本上表示挂起的请求,然后在有人想要完成、拒绝或取消它时使用<代码>完成(动作、有效载荷)
我们的fetchCharacterandPlanetPic
代码有点冗长,如果我们使用这样的东西,我们会做很多。因此,让我们创建一个自定义操作符来为我们处理所有这些
// Extend ActionsObservable so we can have our own custom operators.
// In rxjs v6 you won't need to do this as it uses "pipeable" aka "lettable"
// operators instead of using prototype-based methods.
// https://github.com/ReactiveX/rxjs/blob/master/doc/pipeable-operators.md
class MyCustomActionsObservable extends ActionsObservable {
takeFulfilledTransaction(input) {
return this
.filter(output =>
output.type === input.type + '_FULFILLED' &&
output.meta.transaction.id === input.meta.transaction.id
)
.take(1);
}
}
// Use our custom ActionsObservable
const adapter = {
input: input$ => new MyCustomActionsObservable(input$),
output: output$ => output$
};
const epicMiddleware = createEpicMiddleware(rootEpic, { adapter });
然后,我们可以在epic中使用该自定义操作符
const fetchCharacterAndPlanetEpic = (action$, store) =>
action$.ofType(GET_CHARACTER_AND_PLANET)
.mergeMap(action =>
action$.takeFulfilledTransaction(action)
.mapTo(getPlanet(action.planetId))
.startWith(getCharacter(action.characterId))
);
这里描述的事务式解决方案是真正的实验性的。在实践中,这些年来我注意到了它的一些缺点,但我还没有考虑如何修复它们。也就是说,总的来说,它在我的应用程序中非常有用。事实上,它也可以用来进行乐观更新和回滚!几年前,我将这个模式和可选的乐观更新内容放入了库中,但我从来没有回过头来给它一些爱,所以使用时要自担风险。它应该被认为是被抛弃的,即使我可能会回来。可能是重复的谢谢!这样,像
GET_CHARACTER
这样的初始操作也被分派到存储区,与我的答案中的解决方案相反。它看起来也更好,而且可能更容易测试。然而,当我读到像“真正的实验性”或“使用风险自负”这样的词时,我不想玩它,因为我可能会遇到一些非常特殊的情况,在这种情况下我会迷路,没有人会帮助我:)它不应该是.startWith(getCharacter(action.characterId)).takeUntil(getCharacter\u FAILURE)
?因为如果“获取角色”以失败告终,epic仍将等待类型(获取角色)的action$。。。如果有人单独调用getCharacterEpic
,它将在getCharacterandPlaneTepic
中触发.mapTo(getPlanetID(action.planetId))
,如果它以成功告终。“让我不想玩它”这是一个想法:)这是一个现有技术的例子,有人,不一定只有你,可以选择使用它。“不应该是…”的确。StackOverflow的答案永远不应该被认为是详尽的,特别是在错误处理方面,而应该给OP足够的时间来解除阻塞。这不是真正的实验。这是开发人员广泛需要的解决方案。感谢forkEpic方法不再适用于“redux observable”:“^1.0.0”“
,它一直在说“操作必须在普通对象中”,我认为这一行是导致它的原因,const actions$=ActionsObservable.of(…actions)代码>@jayphelps?