redux-observable 您提供了 'undefined' 预期流的位置

redux-observable you provided 'undefined' where a stream was expected

我正在使用 fbsdk 在 ajax 请求中获取用户详细信息。所以在 redux-observable 史诗中这样做是有意义的。 fbsdk 请求的方式,它没有 .map().catch() 它需要成功和失败回调:

代码:

export const fetchUserDetailsEpic: Epic<*, *, *> = (
  action$: ActionsObservable<*>,
  store
): Observable<CategoryAction> =>
  action$.ofType(FETCH_USER_DETAILS).mergeMap(() => {
    getDetails(store)
  })

const getDetails = store => {
  console.log(store)
  let req = new GraphRequest(
    '/me',
    {
      httpMethod: 'GET',
      version: 'v2.5',
      parameters: {
        fields: {
          string: 'email,first_name,last_name'
        }
      }
    },
    (err, res) => {
      if (err) {
        store.dispatch(fetchUserDetailsRejected(err))
      } else {
        store.dispatch(fetchUserDetailsFulfilled(res))
      }
    }
  )

  return new GraphRequestManager().addRequest(req).start()
}

它给出错误:

TypeError: You provided 'undefined' where a stream was expected. You can provide an Observable, Promise, Array, or Iterable.

我如何return 从史诗中观察到这个错误消失?

尝试 bindCallback 从此 :

const getDetails = (callBack, details) => {
  let req = new GraphRequest(
    '/me',
    {
      httpMethod: 'GET',
      version: 'v2.5',
      parameters: {
        fields: {
          string: 'email,first_name,last_name'
        }
      }
    },
    callBack(details)
  )

  new GraphRequestManager().addRequest(req).start()
}

const someFunction = (options, cb) => {
  if (typeof options === 'function') {
    cb = options
    options = null
  }
  getDetails(cb, null)
}

const getDetailsObservable = Observable.bindCallback(someFunction)

export const fetchUserDetailsEpic: Epic<*, *, *> = (
  action$: ActionsObservable<*>
): Observable<CategoryAction> =>
  action$.ofType(FETCH_USER_DETAILS).mergeMap(() => {
    getDetailsObservable()
      .mergeMap(details => {
        return Observable.of(fetchUserDetailsFulfilled(details))
      })
      .catch(error => Observable.of(fetchUserDetailsRejected(error)))
  })

出现同样的错误

我不太记得在使用 RxJS >= 6 之前 redux-observable 是如何工作的,但我会尽力提供帮助 ;)

首先,你不需要自己调度,redux-observable 会为你完成。 In this article,他们展示了它是如何在引擎盖下工作的,所以他们调用了 dispatch,但你不必这样做。在新的实现中,他们删除了 store 作为支持 state 流的第二个参数:

const epic = (action$, store) => { ... //before
const epic = (action$, state$) => { ... //after

但最重要的是,您遇到的问题是您没有return 动作流,而是单个(已分派的)动作。 From their website:

It is a function which takes a stream of actions and returns a stream of actions.

所以我认为一个快速的解决方案是 return 来自回调的可观察值:

(err, res) => {
  if (err) {
    return Observable.of(fetchUserDetailsRejected(err))
  }
  return Observable.of(fetchUserDetailsFulfilled(res))
}

我会根据您的评论更新答案。祝你好运!

我认为这似乎是 undefined 的可能原因。您将在 mergeMap 回调中返回 undefined

这个

action$.ofType(FETCH_USER_DETAILS).mergeMap(() => {
    getDetails(store)
})

应该是

action$.ofType(FETCH_USER_DETAILS).mergeMap(() => getDetails(store))

action$.ofType(FETCH_USER_DETAILS).mergeMap(() => {
    return getDetails(store)
})

查看 GraphRequestManager .start 的源代码:

start(timeout: ?number) {
  const that = this;
  const callback = (error, result, response) => {
    if (response) {
      that.requestCallbacks.forEach((innerCallback, index, array) => {
        if (innerCallback) {
          innerCallback(response[index][0], response[index][1]);
        }
      });
    }
    if (that.batchCallback) {
      that.batchCallback(error, result);
    }
  };

  NativeGraphRequestManager.start(this.requestBatch, timeout || 0, callback);
}

如您所见,它 return 什么也没做,如此有效 undefined。 Rx mergeMap 需要 Observable 的实例或与之兼容的实例 ()。

由于您调度了进一步的操作,您可以像这样修改您的原始代码:

export const fetchUserDetailsEpic: Epic<*, *, *> = (
  action$: ActionsObservable<*>,
  store
): Observable<CategoryAction> =>
  action$.ofType(FETCH_USER_DETAILS).do(() => { // .mergeMap changed to .do
    getDetails(store)
  })

const getDetails = store => {
  console.log(store)
  let req = new GraphRequest(
    '/me',
    {
      httpMethod: 'GET',
      version: 'v2.5',
      parameters: {
        fields: {
          string: 'email,first_name,last_name'
        }
      }
    },
    (err, res) => {
      if (err) {
        store.dispatch(fetchUserDetailsRejected(err))
      } else {
        store.dispatch(fetchUserDetailsFulfilled(res))
      }
    }
  )

  return new GraphRequestManager().addRequest(req).start()
}

老实说,我发现您的第二次尝试更好/耦合度更低。要使其正常工作,您可以执行以下操作:

const getDetails = Observable.create((observer) => {
  let req = new GraphRequest(
    '/me',
    {
      httpMethod: 'GET',
      version: 'v2.5',
      parameters: {
        fields: {
          string: 'email,first_name,last_name'
        }
      }
    },
    (error, details) => {
      if (error) {
        observer.error(error)
      } else {
        observer.next(details)
        observer.complete()
      }
    }
  )

  new GraphRequestManager().addRequest(req).start()
})

export const fetchUserDetailsEpic: Epic<*, *, *> = (
  action$: ActionsObservable<*>
): Observable<CategoryAction> =>
  action$.ofType(FETCH_USER_DETAILS).mergeMap(() => {
    getDetails()
      .map(details => fetchUserDetailsFulfilled(details)) // regular .map should be enough here
      .catch(error => Observable.of(fetchUserDetailsRejected(error)))
  })

看起来@artur grzesiak 的答案是正确的,但为了完整起见,我认为 bindCallback 可以这样使用。

我对 Artur 的回答的唯一问题是我认为我们不需要捕捉史诗中的错误,因为 fetchUserDetailsRejected 是一个 error-handling 动作(大概是 reducer 处理它适当地)。

我用了这个参考RxJs Static Public Methods: bindCallback

Give it a function f of type f(x, callback) and it will return a function g that when called as g(x) will output an Observable.

// This callback receives the results and returns one or other action (non-observable)
const callback = (err, res) => {
  return err 
    ? fetchUserDetailsRejected(err)
    : fetchUserDetailsFulfilled(res)
}

// Here is the graph request uncluttered by concerns about the epic
const getDetails = (store, callback) => {
  console.log(store)
  let req = new GraphRequest(
    '/me',
    {
      httpMethod: 'GET',
      version: 'v2.5',
      parameters: {
        fields: {
          string: 'email,first_name,last_name'
        }
      }
    },
    callback
  )
  new GraphRequestManager().addRequest(req).start()
}

// This bound function wraps the action returned from callback in an Observable
const getDetails$ = Observable.bindCallback(getDetails).take(1)

// The epic definition using bound callback to return an Observable action
export const fetchUserDetailsEpic: Epic<*, *, *> = 
  (action$: ActionsObservable<*>, store): Observable<CategoryAction> =>
    action$.ofType(FETCH_USER_DETAILS).mergeMap(() => getDetails$(store))