function* fetchData(){
while(true){
const {payload} = yield take('fetchData')
yield call(func,...) // here, mySaga func cannot take actions until yield call finishes, so actions that are dispatched in-between will lost
// therefore, better to use yield fork instead call, like this
yield fock(func,...) //yield fork is a non-blocking method
}
}
An other solution, we can use takeEvery
to take actions simultaneously.
takeEvery
allows multiple actions(like fetchData
) instances to be started concurrently. At a given moment, we can start a new fetchData
task while there are still one or more previous fetchData
tasks which have not yet terminated.
If we want to only get the response of the latest request fired (e.g. to always display the latest version of data) we can use the takeLatest
.
takeLatest
allows only one fetchData task to run at any moment. And it will be the latest started task. If a previous task is still running when another fetchData
task is started, the previous task will be automatically cancelled.
Setting up your root Saga (global error handling)
V1.0 yield all implementation
export default function* rootSaga() {
yield all([
helloSaga(),
watchIncrementAsync()
])
// code after all-effect
}
Here, the all effect is used with an array and your sagas will be executed in parallel.
V2.0 other common root implementation
export default function* root() {
yield fork(saga1)
yield fork(saga2)
yield fork(saga3)
// code after fork-effect
}
This is one popular implementation that behaves similarly to the tutorial root Saga behavior.
fock
is non-blocking and so allows the rootSaga in these two cases to finish while the child sagas are kept running and blocked by their internal effects.
DIFFERENCE all
effect is blocking, so code after all
is executed after all the children sagas completes, while several fork
effects are non-blocking and code after fork
effect gets executed right after yielding fork effects. Another difference is that you can get task descriptors when using fork effects, so in the subsequent code you can cancel/join the forked task via task descriptors.
V3.0 Nesting fork effects in all effect
const [task1, task2, task3] = yield all([ fork(saga1), fork(saga2), fork(saga3) ])
By doing so, you can get an array of task descriptors.
Uncaught errors from forked tasks bubble to the parent task and thus abort it (and all its child tasks) - they cannot be caught by the parent task.
4.0 Keeping the root alive
Implementations above aren’t terribly practical because rootSaga will terminate on the first error in any individual child effect or saga and crash the whole app!
USE spawn
: spawn
is an effect that will disconnect your child saga from its parent, allowing it to fail without crashing it’s parent.
The spawn
effect might be considered similar to Error Boundaries
in React in that it can be used as extra safety measure at some level of the saga tree, cutting off a single feature or something and not letting the whole app crash.
export default function* root() {
yield spawn(saga1)
yield spawn(saga2)
yield spawn(saga3)
}
5.0 Keeping the root alive
To make sagas to be able to restart in the event of failure.
function* rootSaga () {
const sagas = [
saga1,
saga2,
saga3,
];
yield all(
sagas.map((saga) => {
return spawn(function*() {
while (true) {
try {
yield call(saga)
break
} catch (e) {
if (__DEV__) {
/*eslint-disable no-console*/
console.warn('rootSaga catch an error! ', e)
}
}
yield delay(1000); // Optional. Avoid infinite failures blocking app TODO use backoff retry policy...
}
})
})
)
}
one example for saga error handling
- Saga Error Handling Example #644
- Considering simultaneous same errors when restarting sagas, so we use
setTimeout
andisSyncError
flag. - Another idea, we can set
maximum restart time
as a protect mechanism.
export default function* root() {
yield fork(startup)
const sagas = [nextRedditChange, invalidateReddit]
yield sagas.map(saga =>
spawn(function* () {
let isSyncError = false
while (!isSyncError) {
isSyncError = true
try {
setTimeout(() => isSyncError = false)
yield call(saga)
} catch (e) {
if (isSyncError) {
throw new Error(saga.name + ' was terminated because it threw an exception on startup.')
}
yield put(actions.setError(e.message))
}
}
})
)
}
How to create query cancel timeout
use race
. REF
function* sendRequest(request) {
try {
const res = yield call(api, request)
return { res }
} catch (err) {
return { err }
}
}
function* handleRequest(request) {
const task = yield fork(sendRequest, request)
const { res, timeout } = yield race({
res: join(task),
timeout: call(delay, 10 * 1000)
})
if (timeout) {
yield put({ type: 'REQUEST_TIMEOUT' })
return
}
if (res.err) {
yield put({ type: 'REQUEST_ERROR', err: res.err })
return
}
yield put({ type: 'REQUEST_SUCCESS', result: res.res })
}