takeEvery and takeLatest. Why? When to use? Use simultaneously?
Asked Answered
W

4

35

I am not clear in when to use takeEvery and when to use takeLatest ? in redux-saga.

I got a basic difference by reading the official documentation. But what is the use of creating concurrent actions in takeEvery (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 while the fetchUser fired on the first one hasn't yet terminated)

import { takeEvery } from `redux-saga/effects`

function* fetchUser(action) {
  ...
}

function* watchFetchUser() {
  yield takeEvery('USER_REQUESTED', fetchUser)
}

Can anyone please explain. As I am completely new to redux-saga.

Thanks in advance.

Wildfowl answered 24/5, 2020 at 9:57 Comment(0)
R
25

This is something you really need to think about per use case.

These are some cases when you might use takeEvery.

  1. For sagas that are not asynchronous and so there is no reason to cancel them. takeLatest would work here as well but it might give false indication when reading code that there is something to cancel.

  2. Sometimes when the action differs in some way each time. E.g. imagine you have a movie and you are adding tags with genre of the movie. Each time the action is triggered you get a different genre, even though it is the same action type. The user can add genres quicker than you get response from the server. But just because you added multiple genres quickly doesn't mean you want to stop the saga adding the previous one.

  3. Cheap implementation of when you have the same load action for multiple different items. E.g. You have list of movies and each has "load detail" button. (Let's ignore the fact that you should probably hide or disable the button once the loading starts). The data are always the same for one movie but differs in between them. When you click on load detail for movie 1 you don't want to cancel loading of data for movie 2. Since it is a dynamic list of movies the action type is the same every time, the difference will be probably something like id of the movie in the action.

    In ideal implementation you should probably cancel the previous load saga for the same movie/id, but that will require more complicated code and so if you are doing only some simple app you might decide to ignore that and just allow to run the sagas for same movie multiple times. That is why I call it "cheap implementation".

Realism answered 24/5, 2020 at 11:10 Comment(1)
Solid answer - I hope you don't mind if I elaborate a bit on details as well in my answer below ;)Jezabelle
J
36

Though @Martin Kadlec's solid answers covers the question, I want to elaborate a bit more on the details and differences on takeEvery and takeLatest and when they might be used so you can derive the possible use-cases of them.

TLDR:

You can use takeEvery when the returns of all previous tasks are needed. For example fetching temperature and humidity data from a weatherstation over a certain period to be stored in a database and displayed as a graph - in this case all previous sagas and their return-values are of interset and not only the latest.

You can use takeLatest if i.E. a internal/external instance or a user of an interface could trigger multiple consecutive actions and only the conclusion of the last value is desireable. A good example would be rapid calls to a broker-API for a live-ticker for stock-values where only the latest/most recent value is of interest.

DETAILED:

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.

takeEvery:

The most common takeEvery function is very similar to redux-thunk in its behaviour and methodology. It's basically a wrapper for yield take of a pattern or channel and yield fork.

The clue about takeEvery is that it allows multiple instances of a defined action/task (such as fetchSomeThing in the example below) to be started concurrently/simultaniously.

Unlike takeLatest you can start a new fetchSomeThing task while one or more previous instances of fetchSomeThing have not yet been completed/terminated, therefore are still pending. Keep in mind that there is no guarantee that the tasks will terminate/complete in the same order they were started. To handle out of order responses, you could use takeLatest.

From the official docs:

takeEvery(pattern, saga, ...args)

Spawns a saga on each action dispatched to the Store that matches pattern.

  • pattern: String | Array | Function
  • saga: Function - a Generator function
  • 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)

You can also pass in a channel as argument instead of a pattern resulting in the same behaviour as takeEvery(pattern, saga, ...args).

takeLatest:

The takeLatest helper function in contrast only gets the response of the latest request that was fired and can be seen as a wrapper for yield take of a pattern or channel and an additional if-statement checking if a lastTask is present (a previous task that is still pending), which will then be terminated via a yield cancel and a subsequent yield fork that will spawn the current task/action.

From the official docs:

takeLatest(pattern, saga, ...args)

Will only get the response of the latest request fired.

  • pattern: String | Array | Function
  • saga: Function - a Generator function
  • args: Array - arguments to be passed to the started task. takeLatest will add the incoming action to the argument list (i.e. the action will be the last argument provided to saga)

Similar to takeEvery you can also pass in a channel as argument instead of a pattern.

Jezabelle answered 28/10, 2020 at 17:36 Comment(0)
R
25

This is something you really need to think about per use case.

These are some cases when you might use takeEvery.

  1. For sagas that are not asynchronous and so there is no reason to cancel them. takeLatest would work here as well but it might give false indication when reading code that there is something to cancel.

  2. Sometimes when the action differs in some way each time. E.g. imagine you have a movie and you are adding tags with genre of the movie. Each time the action is triggered you get a different genre, even though it is the same action type. The user can add genres quicker than you get response from the server. But just because you added multiple genres quickly doesn't mean you want to stop the saga adding the previous one.

  3. Cheap implementation of when you have the same load action for multiple different items. E.g. You have list of movies and each has "load detail" button. (Let's ignore the fact that you should probably hide or disable the button once the loading starts). The data are always the same for one movie but differs in between them. When you click on load detail for movie 1 you don't want to cancel loading of data for movie 2. Since it is a dynamic list of movies the action type is the same every time, the difference will be probably something like id of the movie in the action.

    In ideal implementation you should probably cancel the previous load saga for the same movie/id, but that will require more complicated code and so if you are doing only some simple app you might decide to ignore that and just allow to run the sagas for same movie multiple times. That is why I call it "cheap implementation".

Realism answered 24/5, 2020 at 11:10 Comment(1)
Solid answer - I hope you don't mind if I elaborate a bit on details as well in my answer below ;)Jezabelle
D
10

To summarize in a few words,

  • takeEvery allows concurrent actions to be handled. 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.
  • takeLatest instead start a new fetchUser task on each dispatched USER_REQUESTED action. Since takeLatest cancels any pending task started previously, we ensure that if a user triggers multiple consecutive USER_REQUESTED actions rapidly, we'll only conclude with the latest action

Docu: https://redux-saga.js.org/docs/api/

Disallow answered 28/10, 2021 at 18:58 Comment(0)
Z
5
  • 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. It will also be the work that was started most recently. If a new fetchData job is started while a previous task is still running, the previous work will be terminated immediately.

Zildjian answered 10/1, 2022 at 9:46 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.