Best way to listen for actions in saga: `while(true) take()` vs `while(take())` vs. `takeEvery()`
Asked Answered
R

1

18

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?

Rezzani answered 20/12, 2017 at 15:25 Comment(4)
They dont do the same so its unneccessary to compare them. Choose the right tool for the right job, as alwaysWira
1 and 2 are the same. 3 is different, it processes 'USER_DETAILS_REQUESTED' concurrently. i.e., it can handle many actions simultaneouslyTenney
@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.Granddad
@Granddad 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()Tenney
N
25

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.

Nut answered 21/6, 2018 at 9:17 Comment(1)
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 payloadRezzani

© 2022 - 2024 — McMap. All rights reserved.