I just got into redux-saga and I'm a bit confused on Watchers and Generators
Take for example this code below, that is the entry-point of my sagas file
function* employeesSaga() {
yield all([
takeEvery(getType(employeeActions.login.request), handleLoginRequest),
takeEvery(getType(employeeActions.verifyLogin.request), handleVerifyLoginRequest),
takeEvery(getType(employeeActions.logout), handleLogout)
]);
}
I am directly wiring each redux call to the corresponding handler.
But some people use watchers, and then they call the handler in that generator. What is the purpose of doing that? Should I use that pattern?
Also, I noticed some people wrap their entire handler with a while(true)
, is that necessary? Because my code works fine without that too...
redux-saga is a library that aims to make application side effects (i.e. asynchronous things like data fetching and impure things like accessing the browser cache) easier to manage, more efficient to execute, easy to test, and better at handling failures.
In the above example, takeEvery allows multiple 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.
takeLatest(pattern, saga, ... And automatically cancels any previous saga task started previously if it's still running. Each time an action is dispatched to the store. And if this action matches pattern , takeLatest starts a new saga task in the background.
Redux Saga is a middleware library used to allow a Redux store to interact with resources outside of itself asynchronously. This includes making HTTP requests to external services, accessing browser storage, and executing I/O operations. These operations are also known as side effects.
On the first question
It may be just a matter of readability.
Watchers aren't a real "pattern", they just make your code more explicit about its intentions:
function* watchLoginRequest() {
yield takeEvery(getType(employeeActions.login.request), handleLoginRequest)
}
function* watchVerifyLoginRequest() {
yield takeEvery(getType(employeeActions.verifyLogin.request), handleVerifyLoginRequest)
}
function* watchLogout() {
yield takeEvery(getType(employeeActions.logout), handleLogout)
}
function* startWatchers() {
yield call(watchLoginRequest)
yield call(watchVerifyLoginRequest)
yield call(watchLogout)
}
function* employeesSaga() {
yield call(startWatchers)
}
Second question
Probably you're talking about this kind of flow:
function* watchLoginRequest() {
while (true) {
const action = yield take(getType(employeeActions.login.request))
yield call(handleLoginRequest, action)
}
}
The difference:
The while(true)-take-call flow does not allow executing two instances of a single handler concurrently. It takes one action, then blocks on the call, until the handleLoginRequest()
has finished. If the user clicks the login button before the handler has completed, the correnponding actions are missed.
The takeEvery() flow does allow concurrent handler execution. This might not be what you want.
Redux-saga docs tell how takeEvery()
is implemented under the hood:
const takeEvery = (patternOrChannel, saga, ...args) => fork(function*() {
while (true) {
const action = yield take(patternOrChannel)
yield fork(saga, ...args.concat(action))
}
})
You see, takeEvery()
is non-blocking itself (a fork), and it executes the handler in a non-blocking way (a fork).
A third option
There is also takeLatest()
, which does allow concurrent handler execution, but if there is a previous instance of a handler executing, it cancels it. Redux-saga docs provide its internal implementation, too.
The while(true)-take-call is the best as a login flow, I think. You probably do not want concurrent logins. If you block concurrent logins on UI level, though, these flows are equivalent, but while(true)-take-call is the most explicit and readable.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With