admin管理员组文章数量:1185210
Is there a way to debounce in Redux-Saga where subsequent calls are queued up behind the same delay, which keeps getting bumped by each new task added to queue. Similar to lodash's debounce .
I currently have something similar to redux-saga's debounce but removed the cancel part as I still want to action each task, I just want to bundle up all events to fire in a single thread later.
What I have currently:
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);
}
}
I tried assigning fork to variable and then checking if that was running (.isRunning()) but didn't know how I could postpone that fork by another delay.
Is there a way to debounce in Redux-Saga where subsequent calls are queued up behind the same delay, which keeps getting bumped by each new task added to queue. Similar to lodash's debounce https://lodash.com/docs#debounce.
I currently have something similar to redux-saga's debounce but removed the cancel part as I still want to action each task, I just want to bundle up all events to fire in a single thread later.
What I have currently:
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);
}
}
I tried assigning fork to variable and then checking if that was running (.isRunning()) but didn't know how I could postpone that fork by another delay.
Share Improve this question asked May 23, 2016 at 13:15 LabithiotisLabithiotis 4,1297 gold badges31 silver badges47 bronze badges6 Answers
Reset to default 16Redux saga now has a debounce function/effect:
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)
}
I was about to write an example using an array as a queue to store the actions to buffer, along with a setTimeout to flush the queue calling call() on each of them (and then cancelling the timeout accordingly if a new action comes in before it expires), but I noticed that now redux-saga supports Channels:
https://yelouafi.github.io/redux-saga/docs/advanced/Channels.html
They also have a built-in buffer to store actions while the saga is busy. Here the trick is to replace the api call from the docs example with your delay
function, so that the saga is "busy" and will buffer actions for you.
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);
}
}
You also have different buffer strategies to choose from.
Please note I'm not 100% sure my example will work in your case as I never used channels before, but hopefully you can adapt it to your problem.
If you schedule tasks individually for execution, they will fire all after a debounce period, however they wont be bundled in the same event loop; instead each delay call will schedule its execution in its own loop. If I'm not mistaken, what you want is to fire the grouped tasks in the same event loop after same delay.
The channel API doesn't offer actually a non-blocking take (and I think your case above suggests we should add it to the library). But you can implement a similar solution without much difficulty.
A possible solution is to split the work into 2 daemon sagas: the 1st will continually watch for actions and put debounced tasks in a shared queue. The 2nd will continually: 1. sleep for some time, 2. wake up and forks tasks for all queued actions until the queue is empty, then sleep again.
For example
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)
}
}
}
I don't know what is your application set up, but here is how I made debounce in my project:
I have root saga. It is all 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. I only used delay
. That solved the issue
const debounceAutocomplete = function*() {
yield delay(300); // <= here you debounce <input/> typing
yield put({type: GET_PRODUCTS}); // <= here you takeLatest from <input/>
};
getProductsSaga which makes request
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);
};
I also need setSearchTerm
action creator to update state with search input before request and debounce:
const setSearchTerm = (name, value) => ({
type: SET_SEARCH_TERM,
payload: {
name: name,
value: value
}
});
So in my component I dispatch this:
import { setSearchTerm } from '../Store/actions-sagas';
import { useDispatch } from 'react-redux';
const Component = () => {
const dispatch = useDispatch();
const updateSearchValues = (key, value) => dispatch(setSearchTerm(key, value));
}
You could use takeLatest
with delay
using both, should work
I have implemented this helper effect just like others(takeLatest, takeEvery). It will aggregate the actions dispatched with in 300ms and call the intended saga.
This will be helpful for the scenarios where you get actions with in a sort span of time where calling it in batch manner is expected.
Hoping this will be helpful for someone. This is similar to debounce implementation. This can be improved to debounced queue as well
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);
}
}
});
本文标签: javascriptRedux saga debounce and not just delaycancelStack Overflow
版权声明:本文标题:javascript - Redux saga debounce and not just delaycancel - Stack Overflow 内容由网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:http://www.betaflare.com/web/1738334630a2076682.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
发表评论