【问题标题】:Redux saga debounce and not just delay/cancelRedux saga debounce 而不仅仅是延迟/取消
【发布时间】:2016-05-23 13:15:07
【问题描述】:

有没有办法在 Redux-Saga 中消除抖动,其中后续调用在相同的延迟之后排队,这会不断被添加到队列中的每个新任务碰撞。类似于 lodash 的 debounce https://lodash.com/docs#debounce.

我目前有类似于 redux-saga 的 debounce 的东西,但删除了取消部分,因为我仍然想执行每个任务,我只是想捆绑所有事件以便稍后在单个线程中触发。

我目前拥有的:

const deferTime = 2000;
const delay = (ms) => new Promise(resolve => setTimeout(resolve, ms));

export function* sendClickEvent (event, debounce) {
  if (debounce) {
    yield call(delay, deferTime);
  }
  yield put(action(event));
}

export function* clickSaga () {
  while (true) {
    const action = yield take(WIDGET_CLICKED);
    const state = yield select();
    const debounce = action.meta && action.meta.debounce;
    const payload = getWidgetClickPayload(state, action);
    const defaultData = getDefaultData(state);
    const event = {
      name: payload.name,
      data: Object.assign(defaultData, payload.data)
    };
    yield fork(sendClickEvent, event, debounce);
  }
}

我尝试将 fork 分配给变量,然后检查它是否正在运行 (.isRunning()),但我不知道如何再延迟该 fork。

【问题讨论】:

    标签: javascript reactjs generator redux redux-saga


    【解决方案1】:

    Redux saga 现在有了 debounce 函数/效果:

    import { call, put, debounce } from `redux-saga/effects`
    
    function* fetchAutocomplete(action) {
      const autocompleteProposals = yield call(Api.fetchAutocomplete, action.text)
      yield put({type: 'FETCHED_AUTOCOMPLETE_PROPOSALS', proposals: autocompleteProposals})
    }
    
    function* debounceAutocomplete() {
      yield debounce(1000, 'FETCH_AUTOCOMPLETE', fetchAutocomplete)
    }
    

    【讨论】:

    • 谢谢!这正是我想要的:)
    • 那么,你在哪里打电话给debounceAutocomplete
    • 可以这样设置,使用action的扩展模式yield debounce(5000, action => /_SUCCESS/.test(action.type), extendToken)
    【解决方案2】:

    我正要编写一个示例,使用数组作为队列来存储要缓冲的动作,以及一个 setTimeout 来刷新队列,在每个动作上调用 call()(如果有新动作,则相应地取消超时在过期之前进来),但我注意到现在 redux-saga 支持 Channels:

    https://yelouafi.github.io/redux-saga/docs/advanced/Channels.html

    他们还有一个内置的缓冲区来在 saga 忙时存储动作。这里的诀窍是用您的delay 函数替换文档示例中的 api 调用,这样 saga 就会“忙碌”并且会为您缓冲操作。

    const deferTime = 2000;
    const delay = (ms) => new Promise(resolve => setTimeout(resolve, ms));
    
    export function* sendClickEvent (event) {
      yield put(action(event));
    }
    
    export function* clickSaga () {
      // Create a channel (buffered by default)
      const requestChan = yield actionChannel(WIDGET_CLICKED)
    
      while (true) {
        // Note: we now take actions from the channel (buffered)
        const action = yield take(requestChan) 
    
        const state = yield select();
        const debounce = action.meta && action.meta.debounce;
        const payload = getWidgetClickPayload(state, action);
        const defaultData = getDefaultData(state);
        const event = {
          name: payload.name,
          data: Object.assign(defaultData, payload.data)
        };
        // This should "suspends" the saga and makes it buffer events.
        yield call(delay, deferTime)
    
        yield fork(sendClickEvent, event);
      }
    }
    

    您还可以选择不同的缓冲策略。

    请注意,我不能 100% 确定我的示例是否适用于您的情况,因为我以前从未使用过频道,但希望您可以根据您的问题调整它。

    【讨论】:

      【解决方案3】:

      如果您单独安排任务执行,它们将在去抖动期后全部触发,但它们不会捆绑在同一个事件循环中;相反,每个延迟调用都会在自己的循环中安排其执行。如果我没记错的话,你想要的是在相同的延迟后在相同的事件循环中触发分组任务。

      通道 API 实际上并没有提供非阻塞获取(我认为您上面的案例表明我们应该将其添加到库中)。但是您可以毫不费力地实施类似的解决方案。

      一种可能的解决方案是将工作分成 2 个守护进程 sagas:第一个将持续监视操作并将去抖动的任务放入共享队列中。第二个将持续:1. 休眠一段时间,2. 唤醒并为所有排队的操作分叉任务,直到队列为空,然后再次休眠。

      例如

      import { delay } from 'redux-saga'
      import { take, put, call, fork, select } from 'redux-saga/effects'
      
      const deferTime = 2000;
      
      function* clickSaga () {
        const taskQueue = []
        // fork the worker tasks
        yield fork(worker, taskQueue)
        while (true) {
          const action = yield take(WIDGET_CLICKED);
          const state = yield select();
          const debounce = action.meta && action.meta.debounce;
          const payload = getWidgetClickPayload(state, action);
          const defaultData = getDefaultData(state);
          const event = {
            name: payload.name,
            data: Object.assign(defaultData, payload.data)
          };
      
          if(debounce) {
            // debounce? batch execution
            taskQueue.push({ task: sendClickEvent, event});
          } else {
            // no debounce, execute right now
            yield fork(sendClickEvent, event)
          }
      
        }
      }
      
      function* worker(queue) {
        while(true) {
          // sleep
          yield call(delay, deferTime)
          // after wakeup, flush the batched tasks
          let current
          while(current = queue.shift()) {
            const {task, event} = current
            yield fork(task, event)
          }
        }
      }
      

      【讨论】:

        【解决方案4】:

        我不知道您的应用程序设置了什么,但这是我在项目中进行去抖动的方式:

        我有 root saga。都是takeLatest

        const productsSaga = function*() {
          yield all([
            takeLatest(SET_SEARCH_TERM, debounceAutocomplete), // <= type text in <input /> 
            takeLatest(GET_PRODUCTS, getProductsSaga), // <= request for products
            ... many more effects here
          ]);
        };
        
        

        去抖动自动完成。我只使用了delay。解决了这个问题

        const debounceAutocomplete = function*() {
          yield delay(300); // <= here you debounce <input/> typing
          yield put({type: GET_PRODUCTS}); // <= here you takeLatest from <input/>
        };
        

        getProductsSaga 提出请求

        const getScientificReviewersSaga = function*() {
          yield put(toggleProductsLoading(true));
          const productsCategoryId = yield select(state => state.category.id);
          const state = yield select(selectProductsState);
          const data = {
            page: state.page,
            size: state.pageSize,
            productName: state.productName,
            productColor: state.productColor
          };
          const params = stringify(data);
          yield put({
            types: GET_PRODUCTS_TYPES,
            payload: {
              request: {
                url: `/${API.products}/${productsCategoryId}/products?${params}`,
                method: 'GET'
              }
            }
          } as Actions);
        };
        

        我还需要setSearchTerm 动作创建者在请求和去抖动之前使用搜索输入更新状态:

        const setSearchTerm = (name, value) => ({
          type: SET_SEARCH_TERM,
          payload: {
            name: name,
            value: value
          }
        });
        

        所以在我的组件中我发送这个:

        import { setSearchTerm } from '../Store/actions-sagas';
        import { useDispatch } from 'react-redux';
        
        const Component = () => {
          const dispatch = useDispatch();
          const updateSearchValues = (key, value) => dispatch(setSearchTerm(key, value));
        }
        

        【讨论】:

          【解决方案5】:

          您可以同时使用takeLatestdelay,应该可以使用

          【讨论】:

          • 举个例子会更好
          【解决方案6】:

          我已经实现了这个辅助效果,就像其他人一样(takeLatest,takeEvery)。它将在 300 毫秒内聚合调度的动作并调用预期的 saga。

          这对于您在一段时间内获得操作的场景很有帮助,并且需要以批处理方式调用它。

          希望这对某人有所帮助。这类似于debounce 的实现。这也可以改进为去抖动队列

          export const watchAndAggregate = (pattern, saga, payloadAggregator, ...args) =>
              fork(function*() {
                  while (true) {
                      const action = yield take(pattern);
                      let { payload } = action;
          
                      while (true) {
                          const { debounced, _action } = yield race({
                              debounced: delay(300),
                              _action: take(pattern),
                          });
          
                          if (debounced) {
                              yield call(saga, ...args.concat({ ...action, payload }));
                              break;
                          }
          
                         payload = payloadAggregator(_action);
                      }
                  }
              });
          

          【讨论】:

            猜你喜欢
            • 2021-07-01
            • 2012-04-21
            • 2021-01-22
            • 1970-01-01
            • 1970-01-01
            • 2017-02-22
            • 2019-06-15
            • 1970-01-01
            • 2020-12-18
            相关资源
            最近更新 更多