1
votes

I'm using redux-observable to handle an action:

export const createPaymentMethod =
  (getBraintreeToken: (Object) => Promise<*>, cardholderName: string) => ({
    type: CREATE_PAYMENT_METHOD,
    getBraintreeToken: () => getBraintreeToken({ cardholderName }),
  });

const mapBraintreeError = err => Observable.of({
  type: CREATE_PAYMENT_METHOD + FAILURE,
  error: { response: err.message },
});

export const createPaymentMethodEpic = (action$: any, store: ReduxState) =>
  action$.ofType(CREATE_PAYMENT_METHOD)
    .switchMap(({ getBraintreeToken }) => Observable.fromPromise(getBraintreeToken()))
    .switchMap(({ nonce }) =>
      ajax(api.createPaymentMethod(store.billings.info.customer_id, nonce))
        .mapSuccess(CREATE_PAYMENT_METHOD)
        .mapFailure(CREATE_PAYMENT_METHOD),
    )
    .catch(mapBraintreeError);

What I do is I intentionally make getBraintreeToken() Promise fail. This results in epic execute catch function and returning CREATE_PAYMENT_METHOD + FAILURE action. Which is what I intended.

The problem is when I try to call epic the second time. It doesn't execute...

EDIT: I have converted the epic and it seems to work now, however, I still don't understand why the first example was broken (actually I like more the flat structure of the first epic).

export const createPaymentMethodEpic = (action$: any, store: ReduxState) =>
  action$.ofType(CREATE_PAYMENT_METHOD)
    .switchMap(({ getBraintreeToken }) =>
      Observable.fromPromise(getBraintreeToken())
        .switchMap(({ nonce }) =>
          ajax(api.createPaymentMethod(store.billings.info.customer_id, nonce))
            .mapSuccess(CREATE_PAYMENT_METHOD)
            .mapFailure(CREATE_PAYMENT_METHOD),
        )
        .catch(mapBraintreeError),
    );
1
Not to take anything away from @martin's answer, but I'm wondering if you can use a mapTo or map to avoid using error catching as the primary path. Depends on what's inside getBraintreeToken() and how the error message is generated. Could you post getBraintreeToken() (if still hunting for optimization).Richard Matsen
Hi @RichardMatsen. I get getBraintreeToken from external api, so I would probably have to wrap it in some other promise to avoid catch block...Tomasz Mularczyk

1 Answers

1
votes

The problem with the first version is that the error notification reaches the switchMap operator that invokes its error logic which causes the chain to dispose.

It needs to be like this because an Observable emits zero or one error notifications but never more.

For more detailed explanation see The Observable Contract.

An Observable may make zero or more OnNext notifications, each representing a single emitted item, and it may then follow those emission notifications by either an OnCompleted or an OnError notification, but not both. Upon issuing an OnCompleted or OnError notification, it may not thereafter issue any further notifications.

In the second version of your code you put the catch operator inside a callback to switchMap. The one error notifications rule is applied here as well but the callback is called for every value so even when the inner Observable emits an error it's caught (and converted into next) and then it's replaced by a new inner Observable.