13

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?

Uwe Keim
  • 36,867
  • 50
  • 163
  • 268
Ali Saeed
  • 1,332
  • 1
  • 15
  • 22
  • They dont do the same so its unneccessary to compare them. Choose the right tool for the right job, as always – Jonas Wilms Dec 20 '17 at 15:44
  • 1
    1 and 2 are the same. 3 is different, it processes 'USER_DETAILS_REQUESTED' concurrently. i.e., it can handle many actions simultaneously – AlexM Dec 21 '17 at 03:02
  • @AlexM Does `yield take(…)` never produce a falsy value? Also #2 takes an additional `select` call to access the `userId`, I doubt it does the same as #1. – Bergi Jun 21 '18 at 10:27
  • @Bergi you're right, the code has some differences. But I was talking about the approaches as a whole: `while(true) take()` vs `while(take())` vs. `takeEvery()` – AlexM Jun 22 '18 at 03:39

1 Answers1

18

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.

Benjamin
  • 371
  • 1
  • 4
  • 11
  • Well explained with the code and console. I'd add that 2. is different from 1. in that it doesn't expose the action and it's payload – Ali Saeed Oct 18 '18 at 15:46