I've seen sagas listening for actions in 3 ways:
1. while(true) take()
function* onUserDetailsRequest() {
while(true) {
const { userId } = yield take(USER_DETAILS_REQUESTED);
const response = yield call(fetchUserDetails, userId);
put(USER_DETAILS_RECEIVED, response);
}
}
2. while(take())
function* onUserDetailsRequest() {
while(yield take(USER_DETAILS_REQUESTED)) {
const userId = yield select(userSelectorFn);
const response = yield call(fetchUserDetails, userId);
put(USER_DETAILS_RECEIVED, response);
}
}
3. takeEvery()
function* onUserDetailsRequest() {
yield takeEvery(USER_DETAILS_REQUESTED, function* (action) {
const { userId } = action;
const response = yield call(fetchUserDetails, userId);
put(USER_DETAILS_RECEIVED, response);
}
}
What are the pros and cons of each? And in which scenarios should we use one over another?
takeEvery - enables the use of several fetchData objects at the same time. 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 - Only one fetchData task can be active at any given moment.
TakeEvery is a high-level API built using Take and Fork. TakeEvery doesn't handle out of order responses from tasks. There is no guarantee that the tasks will terminate in the same order they were started. To handle out of order responses, you may consider TakeLatest.
Unlike takeEvery , 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.
The take is just like call and put we saw earlier. It creates another command object that tells the middleware to wait for a specific action. The resulting behavior of the call Effect is the same as when the middleware suspends the Generator until a Promise resolves.
For takeEverySaga and takeSagaWithFork, however, a new task was forked everytime it recieved a testTakeEvery action, so they were sleeping in their own task "thread" and so new action won't get missed. Thus, takeEvery is essentially the same as while (true) + take + fork.
The task of takeSaga is sleeping when the second testTake action dispatched therefore the takeSaga simply ignored the second testTake action. For takeEverySaga and takeSagaWithFork, however, a new task was forked everytime it recieved a testTakeEvery action, so they were sleeping in their own task "thread" and so new action won't get missed.
Spawns a saga on each action dispatched to the Store that matches pattern. args: Array - arguments to be passed to the started task. takeEvery will add the incoming action to the argument list (i.e. the action will be the last argument provided to saga)
Think of takeEvery and takeLatest as helper funtions on top of the lower level API of redux-saga which are wrapping internal operations such as spawning tasks when specific actions are dispatched to the Store. Calling them spawns a saga on each action dispatched to the Store that matches pattern.
To clearify @AlexM's answer with code.
cat test.js
const { createStore, applyMiddleware } =require('redux')
const createSagaMiddleware =require('redux-saga').default
const { takeEvery ,take,fork}=require('redux-saga/effects')
const {delay} =require('redux-saga')
const sagaMiddleware = createSagaMiddleware()
const reducer=(state=[],action)=>{return [...state,action.type];}
const store = createStore(
reducer,
applyMiddleware(sagaMiddleware)
)
function* takeSaga() {
while(true){
const action=yield take('testTake')
console.log(action)
yield delay(1000)
}
}
function* takeEverySaga() {
yield takeEvery('testTakeEvery',function* (action){
console.log(action)
yield delay(1000)
})
}
function* takeSagaWithFork() {
while(true){
const action=yield take('testTakeWithFork')
yield fork(function*(){
console.log(action)
yield delay(1000)
})
}
}
sagaMiddleware.run(takeSaga)
sagaMiddleware.run(takeEverySaga)
sagaMiddleware.run(takeSagaWithFork)
const main=async ()=>{
store.dispatch({type: 'testTake'})
store.dispatch({type: 'testTake'})
store.dispatch({type: 'testTakeEvery'})
store.dispatch({type: 'testTakeEvery'})
store.dispatch({type: 'testTakeWithFork'})
store.dispatch({type: 'testTakeWithFork'})
}
main();
run the above code with node test.js
will output
{ type: 'testTake' }
{ type: 'testTakeEvery' }
{ type: 'testTakeEvery' }
{ type: 'testTakeWithFork' }
{ type: 'testTakeWithFork' }
Do you see the difference? The task of takeSaga
is sleeping when the second testTake
action dispatched therefore the takeSaga
simply ignored the second testTake
action. For takeEverySaga
and takeSagaWithFork
, however, a new task was forked everytime
it recieved a testTakeEvery
action, so they were sleeping in their own task "thread" and so new action won't get missed. Thus, takeEvery
is essentially the same as while(true)
+take
+fork
.
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