Consider the following action type:
interface SaveFoo {
type: 'SAVE_FOO'
payload: {
id: string
value: number
}
}
I have a requirement to implement a saga that will apply throttling selectively. For instance, if the following actions are dispatched:
{ type: 'SAVE_FOO', payload: { id: "a", value: 1 } }
{ type: 'SAVE_FOO', payload: { id: "b", value: 1 } }
{ type: 'SAVE_FOO', payload: { id: "a", value: 2 } }
{ type: 'SAVE_FOO', payload: { id: "a", value: 3 } }
The goal is to initiate handlers for actions 1
and 2
as they have different id
values, while actions 3
and 4
should be queued until action 1
is complete.
This seems like a common scenario but I couldn't find an existing solution. I attempted to implement it manually, but I believe there should be a more efficient approach:
export function splitThrottle<T>(actionCreator: ActionCreator<T>, saga: (action: Action<T>) => SagaIterator, selector: (payload: T) => string) {
const tasks: Record<string, Task> = {}
const bufferLookup: Record<string, Buffer<Action<T>>> = {}
function* queue(action: Action<T>, id: string) {
try {
yield call(saga, action)
} catch (e) {
// error handling
}
const next = bufferLookup[id].take()
if (next) {
tasks[id] = yield call(queue, next, id)
} else {
delete tasks[id]
}
}
return function* () {
while (true) {
const action: Action<T> = yield take(actionCreator)
const id = selector(action.payload)
const existingTask = tasks[id]
if (existingTask) {
bufferLookup[id].put(action)
} else {
let buffer = bufferLookup[id]
if (!buffer) {
buffer = buffers.sliding(1)
bufferLookup[id] = buffer
}
tasks[id] = yield fork(queue, action, id)
}
}
}
}