6

有没有办法在 Redux-Saga 中消除抖动,其中后续调用在相同的延迟之后排队,这会不断被添加到队列中的每个新任务碰撞。类似于 lodash 的去抖动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 再推迟一次。

4

6 回答 6

16

Redux saga 现在具有去抖动功能/效果:

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)
}
于 2019-05-01T09:51:50.973 回答
2

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

我有根传奇。这是全部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));
}
于 2019-12-29T19:46:18.570 回答
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% 确定我的示例是否适用于您的情况,因为我以前从未使用过通道,但希望您可以根据您的问题调整它。

于 2016-05-23T18:32:55.850 回答
2

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

通道 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)
    }
  }
}
于 2016-05-24T16:34:11.890 回答
1

您可以takeLatest同时delay使用两者,应该可以

于 2019-03-27T16:27:26.173 回答
1

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

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

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

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);
            }
        }
    });
于 2020-11-27T08:50:36.630 回答