【问题标题】:Debouncing and cancelling with redux-observable使用 redux-observable 去抖动和取消
【发布时间】:2018-01-12 19:05:16
【问题描述】:

我正在尝试创建一个简单的 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()]))
    ));
};

该代码仅在某些时候有效。根据服务器的响应速度,我相信可能会发生 2 种情况中的 1 种。

场景 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 调用而无需等待?

谢谢!

【问题讨论】:

    标签: rxjs redux-observable


    【解决方案1】:

    您实际上只是对匹配的validateRequestAction 进行去抖,但您的.takeUntil(action$.ofType(validateCancelAction)) 没有任何去抖。我可能是错的,但如果取消操作可能在 之前调度该操作已通过去抖动,那么它打算取消的操作将不会被取消,因为 ajax 请求甚至还没有开始,takeUntil 也没有。通过在您的副作用(在本例中为 ajax)实际开始并且 takeUntil 正在侦听可能的取消之前不允许取消,可以避免这种竞争。

    在您的 UI 中,您不会让用户能够取消,直到设置了 redux 中的某些状态。因为我们的史诗需要告诉 redux 什么时候翻转它,所以我们需要发出一个我们将在 reducer 中监听的动作。

    最简单的方法是使用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,然后使用repeat“重新启动”史诗如果它被取消。因此,当我们取消时,这将关闭所有内容;任何未决的 ajax 和任何未决的去抖动。

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

    值得注意的是,我使用了史诗和重启这两个术语来帮助概念化我们的特定领域,但这主要是普通的 RxJS,因此它通常适用于 redux-observable 之外。 “史诗”只是我们的函数模式的一个词,它接受一个动作流(输入)并返回一个动作流(输出)。

    【讨论】:

    • 感谢您花时间解释我对去抖动操作的误解,这对我正确理解问题有很大帮助。我尝试了您提供的第二种解决方案,并且效果很好!我也不知道startWith,我相信将来会派上用场。非常感谢!
    • 不客气!我强烈建议您花一些时间来真正理解 为什么 它有效 :) 祝你好运!
    • 是的,我现在正在阅读所有文档 =)
    • @jayphelps 我发现如果不使用take 来完成流,第二种方法很难测试。有时这会使测试变得非常人为和冒险。我也尝试使用debounce(_ =&gt; timer(250)) 而不是debounceTime(250) 并将相同的takeUntil 添加到timer,但这确实没有达到我的预期,并且无论如何都完成了去抖。
    • @Pappa 我推荐第一种方法。编辑了我的答案以澄清这一点。
    【解决方案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 将无效。

    【讨论】:

      猜你喜欢
      • 2021-02-20
      • 2017-09-28
      • 1970-01-01
      • 2021-01-03
      • 1970-01-01
      • 1970-01-01
      • 2017-06-12
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多