2

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

  1. 听一个GET_ITEMS_REQUEST动作
  2. 发送 HTTP 请求以获取一些项目
  3. 获取这些项目的 ID 并发送一个GET_ITEM_DETAILS_REQUEST操作,该操作将发送另一个 HTTP 请求以获取这些项目的更多详细信息
  4. 用第二个请求中的细节装饰第一个请求中的项目并发送最终GET_ITEMS_SUCCESS操作,这将更新 redux 状态

从第 3 步到第 4 步是我卡住的地方。我知道如何GET_ITEM_DETAILS_REQUEST使用项目 ID 进行调度,但我不知道如何侦听/订阅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'
      });
    });
}
4

1 回答 1

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 链”。您不希望错误传播得超出应有的范围。

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

有时您确实希望在外链上捕获一个问题,但这是最后一道防线,通常仅适用于不可恢复的错误。

于 2017-02-16T02:04:52.907 回答