Redux-saga - how to cancel forked process on action dispatch - redux

I have a following Saga:
function* interiorFileSaga() {
yield [
takeLatest(wizardActionTypes.UPLOAD_INTERIOR_FILE, handleInteriorFileUpload),
takeLatest(wizardActionTypes.INTERIOR_FILE_PROCESSING, handleInteriorFileProcessing),
]
}
that is responsible for uploading and processing file. Inside the handleInteriorFileProcessing I'm having a while loop, that is checking file processing progress etc. I want to cancel the forked process, when we dispatch a certain action - when user changes page, on component unmount I want to stop the process. I know that cancel effect is taking a process as an argument. Is there a way I can cancel all child processes? What would be the correct syntax here, to cancel handleInteriorFileProcessing process on let's say CANCEL_BACKGROUND_JOB. I'm a Saga beginner and can't find a way to cancel a process in saga that is responsible for forking a few tasks like here.
Best wishes.

Simplest would probably be to do
takeLatest([wizardActionTypes.INTERIOR_FILE_PROCESSING, CANCEL_BACKGROUND_JOB], handleInteriorFileProcessing)
and the check inside your function what actually happened.
This works because takeLatest cancels running tasks from that statement.
An alternative way to cancel things is to use the race effect, which cancels all 'losers'.

Related

Troubleshooting: Redux & Redux Dev Tools -- Action "logjam" -- Actions are not appearing... then appearing all at once on next action

Problem
Actions in my redux store are appearing to log-jam behind one another. I'm iterating through a set of thunks, which each call a number of actions to show they've started, succeeded, etc. When this happens, an action appears for a second in redux dev tools, then is erased.
If I post another action, then all the actions appear all at once, like container ships following the ever-given.
Link to gif of the issue
In this gif I connect to a testing database, afterwards, a number of operations dispatch. I can see those operations in the console, but not devTools. Then, I post another action via the onscreen button, and all the actions flow through at once.
I'm hunting for instances of mutated state, but all reducers destructure state into a new object via:
let newState = {...state}
Any tips?
EDIT:
When I dispatch the same operation from behind a button element, it works just fine. The code that's log jamming is being called by an event listener attached to an event emitter... maybe this has something to do with it?
After debugging, I've traced the problem back to the redux replaceReducer method. I call it 3 times in this sequence. The first and second invocation works fine, but on the third - the store stops receiving actions.
store.injectReducer = (key, asyncReducer) => {
storeTools.dispatchAction({type:"STORE_INJECT_REDUCER_" + key})
store.asyncReducers[key] = asyncReducer;
let combinedReducers = createReducer(store.asyncReducers);
storeTools.dispatchAction({type:"STORE_INJECT_REDUCER_" + key})
store.replaceReducer(combinedReducers);
storeTools.dispatchAction({type:"RESET"})
console.log("replaceReducer")
}
^^^
This code prints actions on the first 2 invocations, but on the third, it prints the first two actions, but not the third.
This bug was caused by invoking "replaceReducer" multiple times within the same thread. From what I now understand - if you call replaceReducer in a forEach loop... you're gunna have a bad time.
My solution was to create a function that stages multiple reducers - then calls replaceReducer once.
May folks from the future benefit from this knowledge.

How to handle error when we use Redux-Saga all effect in a loop?

I have a list of items that need to be downloaded. I have already START_DOWNLOAD action. So, all I need to do call the action in a loop. We are using redux-saga and the code,
yield all([items.map(item => put(initDownload(item)))]);
is working well unless if one of the download is failed. Failing one item fails the entire collection.
How can I start parallel actions in a loop that does not affect each other?

Deduplicating API calls with Redux Saga

In part of my application I need to save an item state to the server.
However sometimes I will trigger a chain of submits each about 10ms away from the other.
State A
State B
State C
State D
In this case I should really only submit State D.
My current solution is to have a takeLatest() on a saga with
function* submitItemStateSaga(action: Action<SubmitItemStatePayload>) {
yield call(delay, THROTTLE_MS);
//saga body
}
This seems kinda of hacky to me. Do you think this is okay, or is there a better way to do it using the inbuilt throttle() function.
Is this 10ms determined by you or by your server's response? If the latter, sooner or later this approach will let you down.
That being said, you can do this
function* submitItemStateSaga(action) {
const { submit, cancel } = yield race({
submit: take('CONFIRM_SUBMISSION'),
cancel: take('CANCEL_SUBMISSION'),
})
if (submit) {
//saga body
}
}
function* actionWatcher() {
yield takeLatest('START_SUBMISSION', submitItemStateSaga)
}
And at the beginning of each submit on your chain you can dispatch the action to cancel the previous one and start the next. And then after you executed your entire chain you dispatch the action to confirm the submission.
It looks like like redux-saga's debounce() fits your use case better. It waits for incoming actions to slow down, then executes a task with the last action.
Both throttle() and debounce() are implemented via primitives like fork() and call(), and the implementations are provided in the docs. When the default behavior doesn't work for you, these implementations are a good start for writing your own custom solution.

Background URLSession on watchOS - what is the cycle?

I have a class with the delegates for a URLSession. I intend to use it with a background configuration. I understand that the handlers are called when a certain event happens, such as didFinishDownloadingTo.
However, I do have the handle function on my ExtensionDelegate class:
func handle( _ handleBackgroundTasks:
Set<WKRefreshBackgroundTask>)
// Sent when the system needs to launch the application in the background
to process tasks. Tasks arrive in a set, so loop through and process each one.
for task in handleBackgroundTasks {
switch task {
case let urlSessionTask as WKURLSessionRefreshBackgroundTask:
I wonder: where should I handle the data I receive after a download? At the didFinishDownloadingTo or at that function on my ExtensionDelegate class, on the appropriate case of the switch statement?
Another question on the same cycle: I read everywhere that one must remember to setTaskCompleted() after going through the background tasks. But I read elsewhere that one should not set a task as completed if the scheduled data transfer hasn't finished. How do I check that?
There is a very good explanation here.enter link description here
It worked when I had an array with my WKURLSessionRefreshBackgroundTask. Then, at the end of my didFinishDownloadingTo, I get the task on that array that has the same sessionIdentifier as the current session.configuration.identifier, and set it as complete.

Asynchronous UDFs and xleventCalculationCanceled

As soon as I press "Enter" after I wrote an asynchronous function into a cell, the async function is correctly called, and Excel raises the event xleventCalculationEnded when the calculation is finished.
However, if I press another cell just after I clicked "Enter" , the event xleventCalculationCanceled is raised, and then the async function is called another time ! Is this behavior normal ? Should I return a result via the Excel12(xlAsyncReturn,...) for the first async call , for the second async call or for both ?
In other word, does the xleventCalculationCanceled event implies that I'm not forced to return a result to Excel ? (using the appropriate asyncHandle)
I'm using async functions to delegate intensive computation in another thread and to not block excel during computation. However if the async function is called automatically two times (as it is the case when the user click another cell without waiting for the first call to finish) then the intensive computation are computed two times for the same input (because the first call -cancelled by excel- still live in the delegate thread...) How do you deal with this problem ?
Two calls for the same function - with the same input - is it a bug ?
Many thanks
What you describe is the normal behaviour. Excel cancels and then restarts the async calculations when there is user interaction (and can do so multiple times).
The documentation suggest that:
xleventCalculationEnded will fire directly after xleventCalculationCanceled, and
You can release any resources allocated during the calculation when xleventCalculationEnded fires. I understand that to include any asyncHandle you might have, and thus that you need not return any result based on the handle.
If your long-running function allows cancellation while in flight, you can cancel the work you do. Otherwise, you might do some internal bookkeeping on what function calls are in flight, and prevent doing the work twice yourself that way.

Resources