редукс-наблюдаемый и rxjs. Преобразование обещания в Observable — epic вызывается только один раз

Я использую redux-observable для обработки действия:

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

Что я делаю, так это намеренно делаю getBraintreeToken() Promise невыполненным. Это приводит к тому, что эпическая функция выполняет catch и возвращает действие CREATE_PAYMENT_METHOD + FAILURE. Что я и намеревался.

Проблема в том, когда я пытаюсь вызвать эпик во второй раз. Он не выполняется...

РЕДАКТИРОВАТЬ: я преобразовал эпик, и теперь он работает, однако я до сих пор не понимаю, почему первый пример был сломан (на самом деле мне больше нравится плоская структура первого эпика).

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),
    );

person Tomasz Mularczyk    schedule 11.01.2018    source источник
comment
Не хочу ничего отвлекать от ответа @martin, но мне интересно, можете ли вы использовать mapTo или map, чтобы избежать использования обнаружения ошибок в качестве основного пути. Зависит от того, что внутри getBraintreeToken() и как генерируется сообщение об ошибке. Не могли бы вы выложить getBraintreeToken() (если все еще охотитесь за оптимизацией).   -  person Richard Matsen    schedule 12.01.2018
comment
Привет @RichardMatsen. Я получаю getBraintreeToken из внешнего API, поэтому мне, вероятно, придется обернуть его в какое-то другое обещание, чтобы избежать блокировки блокировки...   -  person Tomasz Mularczyk    schedule 12.01.2018


Ответы (1)


Проблема с первой версией заключается в том, что уведомление error достигает оператора switchMap, который вызывает свою логику ошибки, которая приводит к удалению цепочки.

Так и должно быть, потому что Observable выдает ноль или одно уведомление error, но никогда больше.

Более подробное объяснение см. в Обзорный контракт.

Observable может сделать ноль или более уведомлений OnNext, каждое из которых представляет один испускаемый элемент, и затем он может следовать за этими уведомлениями об эмиссии с помощью уведомления OnCompleted или OnError, но не обоих. После выдачи уведомления OnCompleted или OnError в дальнейшем он не может выдавать никаких дальнейших уведомлений.

Во второй версии вашего кода вы помещаете оператор catch в обратный вызов switchMap. Здесь также применяется одно правило уведомлений error, но обратный вызов вызывается для каждого значения, поэтому даже когда внутренний Observable выдает ошибку, он перехватывается (и преобразуется в next), а затем заменяется новым внутренним Observable.

person martin    schedule 11.01.2018
comment
Спасибо @martin. Прежде чем я попытаюсь понять это, можно ли сделать второй эпос более «плоским»? Поскольку это начинает напоминать мне ад обратного вызова. - person Tomasz Mularczyk; 11.01.2018