Redux saga debounce而不仅仅是延迟/取消

时间:2016-05-23 13:15:07

标签: javascript reactjs generator redux redux-saga

有没有办法在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())但是我不知道如何通过另一个延迟推迟该分叉。

6 个答案:

答案 0 :(得分:2)

我准备编写一个示例,使用一个数组作为队列来存储缓冲操作,以及一个setTimeout来刷新每个队列上的调用call()(然后在新的动作时相应地取消超时)在它到期之前进入),但我注意到现在redux-saga支持频道:

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

他们还有一个内置缓冲区,用于在传奇忙碌时存储操作。这里的诀窍是用你的delay函数替换docs示例中的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%确定我的示例将适用于您的情况,因为我之前从未使用过频道,但希望您可以根据您的问题进行调整。

答案 1 :(得分:2)

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

渠道API实际上并没有提供非阻塞功能(我认为上面的情况表明我们应该将它添加到库中)。但是你可以毫不费力地实现类似的解决方案。

一种可能的解决方案是将工作分成2个守护进程传奇:第1个将持续监视操作并将去抖的任务放在共享队列中。第二个将持续: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)
    }
  }
}

答案 2 :(得分:1)

您可以将takeLatestdelay一起使用,应该可以使用

答案 3 :(得分:1)

Redux传奇现在具有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)
}

答案 4 :(得分:1)

我已经像其他人一样实现了这种辅助效果(takeLatest,takeEvery)。它将在300毫秒内汇总调度的动作,并调用预期的传奇。

这对于需要在一定时间范围内执行操作并希望以批处理方式调用的情况很有帮助。

希望这对某人有帮助。这类似于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);
            }
        }
    });

答案 5 :(得分:0)

我不知道您的应用程序设置是什么,但是这是我在项目中进行反跳操作的方式:

我有根传奇。都是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
  ]);
};

debounceAutocomplete 。我只使用了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));
}