简体   繁体   English

如何在 RxJS/redux-observable 与 redux-saga 中对操作进行排序?

[英]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".我已经开始深入学习 RxJs,原因之一是掌握redux-observable副作用的方法,虽然我发现 sagas 更方便和“声明性”。 I've already learned merge/flat/concat/switchMap operators, but it didn't help me to figure out how to sequence things in rxjs.我已经学习了merge/flat/concat/switchMap操作符,但它并没有帮助我弄清楚如何在 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 :这是我所说的“排序”的一个例子,在 Timer 应用程序的实例上,可以在一段时间后安排启动,使用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.我不知道如何使用 redux-observable 来实现它。 Please, simply give me peace of code that reproduces same logic but with rxjs operators.请简单地给我代码的平静,这些代码可以使用rxjs操作符重现相同的逻辑。

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.生成器满足迭代器和可迭代协议,这涉及从源中提取值/事件(在本例中为 Redux 操作),并在这些事件到达之前阻止执行。

Observables are push rather than pull. Observable 是而不是拉。 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.此代码复制了 saga 示例中的行为。

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.重要的是,即使我们正在创建和命名这些 observable 流,它们的行为也是惰性的——在订阅之前它们都不会被“激活”,当你向redux-observable中间件提供这个史诗功能时就会发生这种情况。

I assume take() return an observable, haven't test the code.我假设take()返回一个 observable,还没有测试代码。 It can probably be transformed to rx fashion like below.它可能可以转换为 rx 时尚,如下所示。

The key here is repeat() and takeUntil()这里的关键是repeat()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)
                ))
            )
            /////////////////////
        )
    )

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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