Дождитесь последовательности действий с Redux Observable

У меня есть вариант использования, когда мне нужно дождаться последовательности действий, прежде чем я отправлю другую с помощью Redux Observables. Я видел некоторые подобные вопросы, но я не могу понять, как я могу использовать эти подходы для моего конкретного варианта использования.

По сути, я хочу сделать что-то вроде этого:

action$
  .ofType(PAGINATION_CLICKED) // This action occurred.
  .ofType(FETCH_SUCCESS) // Then this action occurred after.
  .map(() => analyticsAction()); // Dispatch analytics.

Я также хотел бы отменить и запустить эту последовательность снова, если, например, срабатывает другое действие типа FETCH_ERROR.


person Matt Derrick    schedule 07.08.2017    source источник


Ответы (1)


Отличный вопрос. Важным моментом является то, что action$ — это горячий/многоадресный поток всех действий по мере их отправки (это тема). Поскольку жарко, мы можем комбинировать его несколько раз, и все они будут слушать один и тот же поток действий.

// uses switchMap so if another PAGINATION_CLICKED comes in
// before FETCH_SUCCESS we start over

action$
  .ofType(PAGINATION_CLICKED)
  .switchMap(() =>
    action$.ofType(FETCH_SUCCESS)
      .take(1) // <-------------------- very important!
      .map(() => analyticsAction())
      .takeUntil(action$.ofType(FETCH_ERROR))
  );

Поэтому каждый раз, когда мы получаем PAGINATION_CLICKED, мы начинаем слушать эту внутреннюю цепочку Observable, которая прослушивает один FETCH_SUCCESS. Важно иметь этот .take(1), потому что в противном случае мы будем продолжать прослушивать более одного FETCH_SUCCESS, что может привести к странным ошибкам, и даже если это не так, обычно рекомендуется брать только то, что вам нужно.

Мы используем takeUntil, чтобы отменить ожидание FETCH_SUCCESS, если сначала получим FETCH_ERROR.


В качестве бонуса, если вы решите, что хотите также выполнить некоторые аналитические действия на основе ошибки, не только начать заново, вы можете использовать race для действительной гонки между двумя потоками. Выигрывает тот, кто излучает первым; другой отписался.

action$
  .ofType(PAGINATION_CLICKED)
  .switchMap(() =>
    Observable.race(
      action$.ofType(FETCH_SUCCESS)
        .take(1)
        .map(() => analyticsAction()),
      action$.ofType(FETCH_ERROR)
        .take(1)
        .map(() => someOtherAnalyticsAction())
    )
  );

Вот то же самое, но с использованием race в качестве оператора экземпляра вместо статического. Это стилистическое предпочтение, которое вы можете выбрать. Они оба делают одно и то же. Используйте тот, который вам более понятен.

action$
  .ofType(PAGINATION_CLICKED)
  .switchMap(() =>
    action$.ofType(FETCH_SUCCESS)
      .map(() => analyticsAction())
      .race(
        action$.ofType(FETCH_ERROR)
          .map(() => someOtherAnalyticsAction())
      )
      .take(1)
  );
person jayphelps    schedule 07.08.2017
comment
Я почти добрался до места! Я закончил с вашим первым примером, но без (теперь явно критического) take(1). В основном это означало, что последовательность игнорировалась после первого PAGINATION_CLICKED. Большое спасибо, отличная поддержка для библиотеки :) - person Matt Derrick; 09.08.2017
comment
Вместо гонки есть ли способ указать, что я хочу, чтобы оба действия вызывались/завершались до создания третьего действия? - person Nick; 10.05.2018
comment
РЕДАКТИРОВАТЬ: nvm, я думаю, что решил это с помощью оператора zip. - person Nick; 10.05.2018
comment
Совет, меняющий жизнь! - person tszarzynski; 06.06.2018
comment
не лучше ли использовать exaustMap вместо switchMap в вашем первом примере кода? Если метод analyticsAction() занимает некоторое время, и мы тем временем получили действие pagination_clicked, он отменит метод analyticsAction(). - person John; 17.06.2018
comment
@Джон, возможно. Все зависит от того, какое поведение хочет человек. Возможно, вам даже понадобится mergeMap. Все зависит от тебя. - person jayphelps; 18.06.2018