使用redux-observable epics通过多个动作来装饰数据

时间:2017-02-16 01:39:11

标签: reactjs redux rxjs redux-observable

我正在与redux-observable挣扎,试图弄清楚如何使用此流程创建史诗:

  1. 聆听GET_ITEMS_REQUEST行动
  2. 发送HTTP请求以获取一些项目
  3. 获取这些项目的ID并发送GET_ITEM_DETAILS_REQUEST操作,该操作将发送另一个HTTP请求以获取这些项目的更多详细信息
  4. 使用第二个请求中的详细信息装饰第一个请求中的项目,并发送最终GET_ITEMS_SUCCESS操作,这将更新redux状态
  5. 从第3步到第4步是我被困的地方。我知道如何使用商品ID发送GET_ITEM_DETAILS_REQUEST,但我不知道如何倾听/订阅GET_ITEM_DETAILS_REQUEST操作以获取商品详情回复。

    到目前为止,我有以下内容:

    function getItemsEpic(action$) {
    
      return action$
        // step 1
        .ofType('GET_ITEMS_REQUEST')
        .mergeMap(() => {
          // step 2
          return Observable.from(Api.getItems())
        })
        .mergeMap((items) => {
          // step 3
          const itemIds = items.map((item) => item.id);
          return Observable.of({
            type: 'GET_ITEM_DETAILS_REQUEST',
            ids: itemIds
          });
        })
        // ... what now?
        .catch(() => {
          return Observable.of({
            type: 'GET_ITEMS_FAILURE'
          });
        });
    }
    

1 个答案:

答案 0 :(得分:1)

一种方法是,在收到这些项目后,开始收听GET_ITEM_DETAILS_FULFILLED,然后立即使用GET_ITEM_DETAILS_REQUEST启动startWith()。另一部史诗会查看细节并发出GET_ITEM_DETAILS_FULFILLED,我们的另一部史诗会耐心等待,然后将两部(物品+细节)压缩在一起。

const getItemDetailsEpic = action$ =>
  action$
    .ofType('GET_ITEM_DETAILS_REQUEST')
    .mergeMap(({ ids }) =>
      Observable.from(Api.getItemDetails(ids))
        .map(details => ({
          type: 'GET_ITEM_DETAILS_FULFILLED',
          details
        }))
    );

const getItemsEpic = action$ =>
  action$
    .ofType('GET_ITEMS_REQUEST')
    .mergeMap(() =>
      Observable.from(Api.getItems())
        .mergeMap(items =>
          action$.ofType('GET_ITEM_DETAILS_FULFILLED')
            .take(1) // don't listen forever! IMPORTANT!
            .map(({ details }) => ({
              type: 'GET_ITEMS_SUCCESS',
              items: items.map((item, i) => ({
                ...item,
                detail: details[i]
                // or the more "safe" `details.find(detail => detail.id === item.id)`
                // if your data structure allows. Might not be necessary if the
                // order is guaranteed to be the same
              }))
            }))
            .startWith({
              type: 'GET_ITEM_DETAILS_REQUEST',
              ids: items.map(item => item.id)
            })
        )
    );

另外,我注意到你将catch()放在外部的Observable链上。这可能不会完全符合您的要求。当错误到达顶部链时,你的整个史诗都将被终止 - 它将不再是将来的GET_ITEMS_REQUEST!这是一个非常重要的区别,我们经常称它为“隔离你的Observable链”#34;。你不希望错误进一步传播。

// GOOD
const somethingEpic = action$ =>
  action$.ofType('SOMETHING')
    .mergeMap(() =>
      somethingThatMayFail()
        .catch(e => Observable.of({
          type: 'STUFF_BROKE_YO',
          payload: e,
          error: true
        }))
    );

// NOT THE SAME THING!
const somethingEpic = action$ =>
  action$.ofType('SOMETHING')
    .mergeMap(() =>
      somethingThatMayFail()
    )
    .catch(e => Observable.of({
      type: 'STUFF_BROKE_YO',
      payload: e,
      error: true
    }));

你有时确实希望在外链上捕获,但这通常只是针对无法恢复的错误。