简体   繁体   中英

How do I sequence actions in RxJS/redux-observable vs redux-saga?

I've started learning RxJs deeply, one of reasons is to master redux-observable side effects approach, tho I find sagas more convenient and "declarative". I've already learned merge/flat/concat/switchMap operators, but it didn't help me to figure out how to sequence things in rxjs.

Here's an example of what I mean by "sequencing", on instance of Timer app where start may be scheduled after some period of time, implemented with redux-saga :

export function* timerSaga() {
  while (true) {
    yield take('START');

    const { startDelay } = yield select(); // scheduled delay

    const [cancelled] = yield race([
      take('CANCEL_START'),
      delay(startDelay)
    ]);

    if (!cancelled) {
      yield race([
        call(function*() {
           while (true) {
             yield delay(10);
             yield put({ type: 'TICK' });
           }
        }),
        take(['STOP', 'RESET']
      ]);
    }
  }
}

I find that example very logically consistent and clear. I have no idea how to implement that with redux-observable. Please, simply give me peace of code that reproduces same logic but with rxjs operators.

Between sagas (generators) and epics (observables), it's important to change the way you think about how events arrive at your code.

Generators satisfy the iterator and iterable protocols, which involve pulling values/events (in this case, Redux actions) from the source, and blocking execution until those events arrive.

Observables are push rather than pull. We describe and name streams of events that we're interested in, and then we subscribe to them. There are no blocking calls because all of our code is triggered by events when they occur.

This code duplicates the behavior in the saga example.

import { interval, timer } from 'rxjs';
import { withLatestFrom, mapTo, exhaustMap, takeUntil } from 'rxjs/operators';
import { ofType } from 'redux-observable';

const myEpic = (action$, state$) => {
  // A stream of all the "cancel start" actions
  const cancelStart$ = action$.pipe(ofType('CANCEL_START'));

  // This observable will emit delayed start events that are not cancelled.
  const delayedCancellableStarts$ = action$.pipe(
    // When a start action occurs...
    ofType('START'), 

    // Grab the latest start delay value from state...
    withLatestFrom(state$, (_, { startDelay }) => startDelay),

    exhaustMap(
      // ...and emit an event after our delay, unless our cancel stream
      // emits first, then do nothing until the next start event arrives.

      // exhaustMap means we ignore all other start events while we handle
      // this one.
      (startDelay) => timer(startDelay).pipe(takeUntil(cancelStart$))
    )
  );

  // On subscribe, emit a tick action every 10ms
  const tick$ = interval(10).pipe(mapTo({ type: 'TICK' }));

  // On subscribe, emit only STOP or RESET actions
  const stopTick$ = action$.pipe(ofType('STOP', 'RESET'));

  // When a start event arrives, start ticking until we get a message to
  // stop. Ignore all start events until we stop ticking.
  return delayedCancellableStarts$.pipe(
    exhaustMap(() => tick$.pipe(takeUntil(stopTick$)))
  );
};

Importantly, even though we're creating and naming these observable streams, their behavior is lazy - none of them are 'activated' until subscribed to, and that happens when you provide this epic function to the redux-observable middleware.

I assume take() return an observable, haven't test the code. It can probably be transformed to rx fashion like below.

The key here is repeat() and takeUntil()

// outter condition for starting ticker
forkJoin(take('START'), select())
    .pipe(
        switchMap(([, startDelay]) =>
            // inner looping ticker
            timer(10).pipe(switchMap(_ => put({type: 'TICK'})), repeat(),
                takeUntil(race(
                    take('CANCEL_START'),
                    delay(startDelay)
                ))
            )
            /////////////////////
        )
    )

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM