使用redux-observable进行去除和取消

时间:2017-08-04 18:45:51

标签: rxjs redux-observable

我正在尝试创建一个简单的redux-observable史诗,它可以去抖动并且可以取消。我的代码:

export const apiValidate = action$ => {
    return action$.ofType(validateRequestAction)
        .debounceTime(250)
        .switchMap((action) => (
            Observable.ajax({
                url: url,
                method: 'GET',
                crossDomain: true,
                headers: {
                    "Content-Type": 'application/json'
                },
                responseType: 'json'
            })
           .map(payload => (new APISuccessValidate()))
           .takeUntil(action$.ofType(validateCancelAction))
           .catch(payload => ([new APIFailureValidate()]))
    ));
};

代码有时只能起作用。根据服务器响应的速度,我相信可能会出现两种情况中的一种。

场景1(有效):

Time 0ms   - Fire validateRequestAction
Time 250ms - Ajax request occurs
Time 251ms - Fire validateCancelAction
Time 501ms - validateCancelAction passes debounce and cancels properly
Nothing else occurs

情景2(已损坏)

Time 0ms   - Fire validateRequestAction
Time 250ms - Ajax request occurs
Time 251ms - Fire validateCancelAction
Time 400ms - Ajax returns, APISuccessValidate action fired
Time 501ms - validateCancelAction passes debounce and there is nothing to cancel

有没有办法可以编写我的史诗,只有validateCancelAction可以绕过debounceTime并取消ajax调用而无需等待?

谢谢!

2 个答案:

答案 0 :(得分:9)

您实际上只是对validateRequestAction的匹配进行了去抖动,但您的.takeUntil(action$.ofType(validateCancelAction))没有任何去抖动。我可能错了,但是如果之前发送取消操作可能会使其超过去抖动,那么它原本要取消的操作将被取消,因为ajax请求尚未开始,也不是takeUntil。通过不允许取消直到您的副作用(在这种情况下是ajax)实际开始并且takeUntil正在倾听可能的取消,可以避免这种竞赛。

在您的UI中,在设置redux中的某个状态之前,您不会让用户取消。由于我们的史诗需要告诉redux何时翻转,我们需要发出一个我们将在reducers中监听的动作。

最简单的方法是使用startWith运算符:

export const apiValidate = action$ => {
    return action$.ofType(validateRequestAction)
        .debounceTime(250)
        .switchMap((action) => (
            Observable.ajax({
                url: url,
                method: 'GET',
                crossDomain: true,
                headers: {
                    "Content-Type": 'application/json'
                },
                responseType: 'json'
            })
          .map(payload => (new APISuccessValidate()))
          .takeUntil(action$.ofType(validateCancelAction))
          .catch(payload => ([new APIFailureValidate()]))
          .startWith({ type: 'validateRequestActionStarted' }) // <-- here
    ));
};

所以在这个例子中,一些reducer会监听validateRequestActionStarted并改变一些状态,UI会知道我们应该让他们能够取消。

一种完全不同的防止这种竞赛的方式 - 但在大多数情况下我不建议 - 完全是takeUntil在顶级流上,然后只是&#34;重启& #34;使用repeat的史诗如果被取消。因此,当我们取消时,这将关闭所有内容;任何未决的ajaxs和任何未决的debounces。

export const apiValidate = action$ => {
    return action$.ofType(validateRequestAction)
        .debounceTime(250)
        .switchMap((action) => (
            Observable.ajax({
                url: url,
                method: 'GET',
                crossDomain: true,
                headers: {
                    "Content-Type": 'application/json'
                },
                responseType: 'json'
            })
          .map(payload => (new APISuccessValidate()))
          .catch(payload => ([new APIFailureValidate()]))
        ))
        .takeUntil(action$.ofType(validateCancelAction))
        .repeat();
};

值得注意的是,我使用术语epic和restart来帮助概念化我们的特定域,但这通常只是普通的RxJS,所以它通常适用于redux-observable之外。一个&#34;史诗&#34;只是我们的函数模式的一个词,它接受一系列动作(输入)并返回一个动作流(输出)。

答案 1 :(得分:2)

我假设您可能有两种情况:

场景1:

您要在收到取消动作后立即取消油门。这意味着您可能想重置第二个流。很好,但可能不是您想要的。

action$ => {
  const requestAction$ = action$.pipe(
    ofType(validateRequestAction),
    share()
  )
  return merge(
    action$.pipe(
      ofType(validateCancelAction),
      mapTo(false)
    ),
    requestAction$.pipe(mapTo(true))
  ).pipe(
    distinctUntilChanged(),
    switchMap(
      condition => 
        condition ? 
          requestAction$.pipe(
            debounceTime(250),
            switchMap(query => sendRequest(query)
          ) : EMPTY
    )
  )

方案2:

您发送取消信号,并同时告知每个未决请求:“嘿,您不允许发送”。有两种方法可以做到这一点:

  • 首先,使用与请求操作相同的延迟来限制取消操作,以使其与请求操作流竞争。

代码:

merge(
  action$.pipe(
    ofType(validateCancelAction),
    debounceTime(250),
    mapTo(undefined)
  ),
  action$.pipe(
    ofType(validateRequestAction),
    debounceTime(250),
    pluck('payload')
  )
).pipe(
  switchMap(
    query => query ? sendRequest(query) : of({ type: validateCancelDone })
  )
)
  • 第二种正确的解决方案是,在分派取消动作时,将状态设置为已取消。每个受限制的操作都必须先检查这种情况,然后才能发出任何请求:

实际上,这只是您要将取消状态存储在流中还是在redux中。我敢打赌,您选择第一个。代码:

export default action$ => 
  combineLatest(
    action$.pipe(
      ofType(validateRequestAction),
      debounceTime(250),
      pluck('payload')
    ),
    merge(
      action$.pipe(
        ofType(validateCancelAction),
        mapTo(false)
      ),
      action$.pipe(
        ofType(validateRequestAction),
        mapTo(true)
      )
    ).pipe(
      distinctUntilChanged()
    )
  ).pipe(
    switchMap(
      ([query, allow]) =>
        allow
          ? sendRequest(query)
          : EMPTY
    )
  )

编辑:

您还需要distinctUntilChanged() allow流,否则debounceTime无效。