web-dev-qa-db-fra.com

redux-observable vous avez fourni 'indéfini' où un flux était attendu

J'utilise le fbsdk pour obtenir les détails de l'utilisateur dans une demande ajax. Il est donc logique de le faire dans une épopée redux-observable. La façon dont la requête fbsdk va, elle n'a pas de .map() et .catch() elle prend les rappels de réussite et d'échec:

code:

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

Il donne l'erreur:

TypeError: Vous avez fourni "non défini" là où un flux était attendu. Vous pouvez fournir un observable, une promesse, un tableau ou un itérable.

Comment renvoyer un observable de l'épopée pour que cette erreur disparaisse?

Tentative de bindCallback à partir de cela réponse SO :

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

Obtenir la même erreur

16
BeniaminoBaggins

Recherche dans le code source de 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);
}

Comme vous pouvez le voir, il ne retourne rien, donc efficacement undefined. Rx mergeMap nécessite une instance d'Observable ou quelque chose de compatible avec lui ( plus d'informations ).

Puisque vous envoyez d'autres actions, vous pouvez modifier votre code d'origine comme ça:

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

Pour être honnête, je trouve que votre deuxième tentative est un peu meilleure/moins couplée. Pour le faire fonctionner, vous pourriez faire quelque chose comme:

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)))
  })
4
artur grzesiak

Il semble que @artur grzesiak ait une réponse correcte, mais pour être complet, c'est ainsi que je pense que bindCallback peut être utilisé.

Le seul problème que j'ai avec la réponse d'Artur est que je ne pense pas que nous ayons besoin de saisir l'erreur dans l'épopée, car fetchUserDetailsRejected est une action de gestion des erreurs (probablement le réducteur la traite de manière appropriée).

J'ai utilisé cette référence Méthodes publiques statiques RxJs: bindCallback

Donnez-lui une fonction f de type f (x, rappel) et il renverra une fonction g qui, lorsqu'elle est appelée comme g(x), affichera un 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))
3
Richard Matsen

Je ne me souviens pas bien comment fonctionnait redux-observable avant d'utiliser RxJS >= 6 mais je vais essayer de vous aider;)

Tout d'abord, vous n'avez pas besoin de vous expédier, redux-observable le fera pour vous. Dans cet article , ils montrent comment cela fonctionne sous le capot, ils appellent donc dispatch, mais ce n'est pas obligatoire. Dans la nouvelle implémentation, ils ont supprimé store comme deuxième argument en faveur d'un flux state:

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

Mais le plus important, le problème que vous rencontrez est que vous ne renvoyez pas un flux d'actions , mais une seule action (envoyée). à partir de leur site Web :

C'est une fonction qui prend un flux d'actions et renvoie un flux d'actions.

Je pense donc qu'une solution rapide serait de renvoyer des observables de votre rappel:

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

Je mettrai à jour la réponse en fonction de vos commentaires. Bonne chance!

2
Sebabouche

Je crois que cela semble être la raison possible de undefined. Vous retournez undefined dans le rappel mergeMap.

Cette

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

devrait être soit

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

ou

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