Redux-Saga Helper Functions
I’ve seen many teammates default to
takeEvery
. That’s a mistake—pick the helper that matches the scenario. The official saga docs are thin, so I read the source, ran tests, and summarized the nuances here.
Helper Functions
takeEvery
Official documentation:
* `takeEvery` allows concurrent actions to be handled. In the example above,
* when a `USER_REQUESTED` action is dispatched, a new `fetchUser` task is
* started even if a previous `fetchUser` is still pending (for example, the
* user clicks on a `Load User` button 2 consecutive times at a rapid rate, the
* 2nd click will dispatch a `USER_REQUESTED` action while the `fetchUser` fired
* on the first one hasn't yet terminated)
*
* `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` below.
Notes
takeEvery
cannot guarantee that effects finish in the same order they were triggered.- Every dispatched action spawns its own effect.
takeLeading
* Spawns a `saga` on each action dispatched to the Store that matches
* `pattern`. After spawning a task once, it blocks until spawned saga completes
* and then starts to listen for a `pattern` again.
*
* In short, `takeLeading` is listening for the actions when it doesn't run a
* saga.
Notes
takeLeading
only runs when there isn’t already a matching saga in flight. If an action arrives while another instance is still running, it’s ignored.
takeLatest
* Spawns a `saga` on each action dispatched to the Store that matches
* `pattern`. 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. If a
* `saga` task was started previously (on the last action dispatched before the
* actual action), and if this task is still running, the task will be
* cancelled.
Notes
- When the action fires and an effect is still running,
takeLatest
cancels the old effect and starts the new one.
Example
function* fetchUserEffects(action) {
console.log(`Run #${action.payload.count}`);
const userInfo = (yield call(getUserInfo)).data;
yield put(setUserInfoAsync(userInfo));
yield delay(1000);
const userHistory = (yield call(getUserHistory)).data;
yield put(setUserHistory(userHistory));
}
count
increments with each click; the first run starts at 1.
takeEvery
Using yield takeEvery('USER_FETCH', fetchUserEffects);
and dispatching USER_FETCH
four times:
Each action spawns its own saga instance—four clicks produce four complete runs.
takeLeading
Switch to takeLeading
and fire the action four times. Only the first run executes:
takeLatest
With takeLatest
, dispatch the action four times. You’ll see four saga starts, but only the last one finishes fully:
Why? The docs explain it: when a new action arrives while the previous saga is still running (for example, waiting at delay
), the previous saga is cancelled. The console shows all four userInfo
logs, but only the final userHistory
completes.
What about in-flight call
s? If an API request is already in progress when a new action arrives, takeLatest
does not abort the network call. Cancellation means the generator stops processing further yielded values; pending operations finish on their own.
Usage Tips
- For searches or queries where newer requests supersede older ones, use
takeLatest
. - For counters or workflows where every action must run, use
takeEvery
. - When the work is idempotent and repeating it is wasteful,
takeLeading
avoids duplicate executions.
In short: choose based on the behavior you need.