Redux-Saga Helper Functions

· 3 min read · 581 Words · -Views -Comments

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

  1. takeEvery cannot guarantee that effects finish in the same order they were triggered.
  2. 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

  1. 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

  1. 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 calls? 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.

References

Authors
Developer, digital product enthusiast, tinkerer, sharer, open source lover