javascript - 在 redux-observable 中组合和排序多个史诗

标签 javascript redux rxjs redux-observable

我有一个问题,我不知道如何解决。

我有两个史诗请求 api 并更新商店:

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));
    });

现在我有一个简单的场景,我想创建第三个结合上述两个 Action 的 Action ,我们称之为 fetchCharacterAndPlanetEpic .我该怎么做?
我认为在许多情况下(在我的情况下),在第二个 Action 开始之前将第一个 Action 的结果发送到商店是很重要的。这对于 Promises 和 redux-thunk 来说可能是微不足道的。 ,但我想不出用rxjs 做的方法和 redux-observable .

谢谢!

最佳答案

Tomasz 的回答有效且有利有弊(最初在 redux-observable#33 中提出)。一个潜在的问题是它使测试变得更加困难,但并非不可能。例如您可能必须使用依赖注入(inject)来注入(inject) fork 史诗的模拟。

在看到他之前,我已经开始输入答案,所以我想我不妨将它发布给后代,以防任何人感兴趣。

我之前还回答了另一个非常相似的问题,可能会有所帮助:How to delay one epic until another has emitted a value

我们可以发出 getCharacter() ,然后等待匹配的 GET_CHARACTER_SUCCESS在我们发出 getPlanet() 之前.

const fetchCharacterAndPlanetEpic = (action$, store) =>
  action$.ofType(GET_CHARACTER_AND_PLANET)
    .mergeMap(({ characterId, planetId }) =>
      action$.ofType(GET_CHARACTER_SUCCESS)
        .filter(action => action.payload.id === characterId) // just in case
        .take(1)
        .mapTo(getPlanet(planetId))
        .startWith(getCharacter(characterId))
    );

这种方法的一个潜在负面因素是理论上 GET_CHARACTER_SUCCESS这个史诗收到的可能与我们正在等待的不同。过滤器action.payload.id === characterId check 主要可以保护您免受这种情况的影响,因为如果它具有相同的 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))
    );

关键细节是初始“挂起”操作在元对象中拥有唯一的事务 ID。因此,初始操作基本上代表待处理的请求,然后在有人想要完成、拒绝或取消它时使用。 fulfill(action, payload)
我们的fetchCharacterAndPlanetEpic代码有点冗长,如果我们使用这样的东西,我们会做很多。因此,让我们创建一个自定义运算符来为我们处理这一切。
// 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 });

然后我们可以在我们的史诗中使用该自定义运算符漂亮而干净
const fetchCharacterAndPlanetEpic = (action$, store) =>
  action$.ofType(GET_CHARACTER_AND_PLANET)
    .mergeMap(action =>
      action$.takeFulfilledTransaction(action)
        .mapTo(getPlanet(action.planetId))
        .startWith(getCharacter(action.characterId))
    );

此处描述的事务型解决方案是 真正的实验性 .在实践中,多年来我注意到了一些问题,但我还没有开始思考如何解决它们。也就是说,总的来说,它对我的​​应用程序非常有帮助。事实上,它也可以用来做乐观更新和回滚!几年前,我将这种模式和可选的乐观更新内容放入库中 redux-transaction但我从来没有回过头来给它一些爱,所以使用风险自负 .它应该被视为放弃,即使我可能会回到它。

关于javascript - 在 redux-observable 中组合和排序多个史诗,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/48261438/

相关文章:

javascript - d3.randomInt 不是函数

reactive-programming - RX 中没有主题的反馈循环

javascript - 同一 flatMap 方法中的多个 http 请求

javascript - 如何使用 React-router v4 以编程方式从操作创建器进行导航?

javascript - 开放层 3 : How to calculate distance between 2 points?

javascript - Redux - 我已经改变了我的状态,之后如何让事情发生?

javascript - 使用 spread 将我从 API 获取的对象添加到我的初始数据源中

javascript - 在for循环Angular2 Django中获取多个可观察请求

javascript - 为什么动态 <option> 附加到两个下拉框?

javascript - 无法访问 actions/api 服务中的 redux 存储(没有 connect() func)