如何从redux-observable调度多个动作?

时间:2017-12-25 02:02:46

标签: javascript redux rxjs redux-observable

我想从一个redux-observable史诗派遣多个动作。我该怎么做?我最初是以

开头的
const addCategoryEpic = action$ => {
  return action$.ofType(ADD_CATEGORY_REQUEST)
    .switchMap((action) => {
      const db = firebase.firestore()
      const user = firebase.auth().currentUser
      return db.collection('categories')
        .doc()
        .set({
          name: action.payload.name,
          uid: user.uid
        })
        .then(() => {
          return { type: ADD_CATEGORY_SUCCESS }
        })
        .catch(err => {
          return { type: ADD_CATEGORY_ERROR, payload: err }
        })
    })
}

现在,我还想刷新列表(ADD_CATEGORY_SUCCESS),而不仅仅是调度GET_CATEGORIES_REQUEST。我尝试过很多东西,但总是得到

  

动作必须是普通对象。使用自定义中间件进行异步操作

例如:

const addCategoryEpic = action$ => {
  return action$.ofType(ADD_CATEGORY_REQUEST)
    .switchMap((action) => {
      const db = firebase.firestore()
      const user = firebase.auth().currentUser
      return db.collection('categories')
        .doc()
        .set({
          name: action.payload.name,
          uid: user.uid
        })
        .then(() => {
          return Observable.concat([
            { type: ADD_CATEGORY_SUCCESS },
            { type: GET_CATEGORIES_REQUEST }
          ])
        })
        .catch(err => {
          return { type: ADD_CATEGORY_ERROR, payload: err }
        })
    })
}

或将switchMap更改为mergeMap

5 个答案:

答案 0 :(得分:5)

问题是你的switchMap内部会返回一个Promise,它本身会解析为一个concat Observable; Promise<ConcatObservable>switchMap运算符将侦听Promise,然后按原样发出ConcatObservable,然后由redux-observable提供给store.dispatchrootEpic(action$, store).subscribe(store.dispatch)。由于调度Observable没有意义,这就是为什么你得到错误的原因,即操作必须是普通对象。

你的史诗发出的内容必须始终是普通的旧JavaScript动作对象,即{ type: string }(除非你有其他中间件来处理其他事情)

由于Promise只发出一个值,我们不能使用它们发出两个动作,我们需要使用Observables。因此,让我们首先将我们的Promise转换为我们可以使用的Observable:

const response = db.collection('categories')
  .doc()
  .set({
    name: action.payload.name,
    uid: user.uid
  })

// wrap the Promise as an Observable
const result = Observable.from(response)

现在我们需要将会发出单个值的Observable映射到多个动作中。 map运算符不会执行一对多运算,而是我们要使用mergeMapswitchMapconcatMap或{{1}之一}。在这个非常具体的情况下,我们选择哪一个并不重要,因为我们将它应用于(包装Promise)的Observable将只发出一个值然后完成()。也就是说,了解这些运营商之间的差异至关重要,所以绝对需要一些时间来研究它们。

我将使用exhaustMap(同样,在特定的案例中并不重要)。由于mergeMap期望我们返回&#34;流&#34; (一个Observable,Promise,iterator或数组)我将使用mergeMap来创建我们想要发出的两个动作的Observable。

Observable.of

这两个动作将按照我提供的顺序同步和顺序发出。

我们也需要添加错误处理,因此我们将使用RxJS中的Observable.from(response) .mergeMap(() => Observable.of( { type: ADD_CATEGORY_SUCCESS }, { type: GET_CATEGORIES_REQUEST } )) 运算符 - 它与Promise上的catch方法之间的差异很重要,但在外面这个问题的范围。

catch

把它们放在一起,我们会有这样的事情:

Observable.from(response)
  .mergeMap(() => Observable.of(
    { type: ADD_CATEGORY_SUCCESS },
    { type: GET_CATEGORIES_REQUEST }
  ))
  .catch(err => Observable.of(
    { type: ADD_CATEGORY_ERROR, payload: err }
  ))

虽然这可以解决你的问题,但是多个reducers可以从同一个动作进行状态更改,这通常是应该做的事情。顺序发出两个动作通常是一种反模式。

那就是说,这在编程中很常见,这不是绝对的规则。肯定有时候单独行动更有意义,但它们是例外。您可以更好地了解这是否是特殊情况之一。请记住这一点。

答案 1 :(得分:2)

为什么使用Observable.concat? SwitchMap从其回调函数等待包含值(在我们的例子中是动作数组)的Observable或Promise。所以不需要在返回的promise成功处理程序中返回Observable。 试试这个:

const addCategoryEpic = action$ => {
  return action$.ofType(ADD_CATEGORY_REQUEST)
    .switchMap((action) => {
      const db = firebase.firestore()
      const user = firebase.auth().currentUser
      return db.collection('categories')
        .doc()
        .set({
          name: action.payload.name,
          uid: user.uid
        })
        .then(() => {
          return ([
            { type: ADD_CATEGORY_SUCCESS },
            { type: GET_CATEGORIES_REQUEST }
          ])
        })
        .catch(err => {
          return { type: ADD_CATEGORY_ERROR, payload: err }
        })
    })
}

答案 2 :(得分:1)

您也可以尝试在史诗中使用商店

const addCategoryEpic = (action$, store) => {
  return action$.ofType(ADD_CATEGORY_REQUEST)
    .switchMap((action) => {
      const db = firebase.firestore()
      const user = firebase.auth().currentUser
      const query = db.collection('categories')
        .doc()
        .set({
          name: action.payload.name,
          uid: user.uid
        })
       return Observable.fromPromise(query)
    }).map((result) => {
      store.dispatch({ type: ADD_CATEGORY_SUCCESS });
      return ({ type: GET_CATEGORIES_REQUEST })
   })
  .catch(err => {
     return { type: ADD_CATEGORY_ERROR, payload: err }
   })
}

答案 3 :(得分:0)

我发现我应该使用Observable.fromPromise创建一个observable,然后使用flatMap进行多项操作

const addCategoryEpic = action$ => {
  return action$.ofType(ADD_CATEGORY_REQUEST)
    .switchMap((action) => {
      const db = firebase.firestore()
      const user = firebase.auth().currentUser
      const query = db.collection('categories')
        .doc()
        .set({
          name: action.payload.name,
          uid: user.uid
        })
      return Observable.fromPromise(query)
        .flatMap(() => ([
          { type: ADD_CATEGORY_SUCCESS },
          { type: GET_CATEGORIES_REQUEST }
        ]))
        .catch(err => {
          return { type: ADD_CATEGORY_ERROR, payload: err }
        })
    })
}

答案 4 :(得分:0)

对于RxJs6:

action$.pipe(
  concatMap(a => of(Action1, Action2, Action3))
)

请注意,我们有concatMapmergeMapswitchMap可以完成工作,区别在于:https://www.learnrxjs.io/operators/transformation/mergemap.html