Some potential benefits and use cases using thunks for async logic
Two years ago, I addressed a number of concerns about using both thunks and sagas for async logic in my post Thoughts on Thunks, Sagas, Abstraction, and Reusability.
I continue to see a number of folks argue that thunks lead to code that is "unmaintainable and untestable", and I want to lay out some reasons why I don't think that is the case.
I'd agree that thunks are somewhat less testable, but it also depends on what you want to try to test and how you want to test it. For example, thunks that make AJAX calls can be tested by mocking out whatever your AJAX lib is, whether it be via something like
jest.mock(), or writing your thunks to use the
extraArgument setup option and injecting a service object for it to use for the API calls, and that can be replaced with a mock version in a test.
It's certainly a different approach to testing logic than running a generator function and asserting that each yielded effect is as expected, but it's still a valid way to test the logic.
On the other hand, I've seen a number of folks complain that testing sagas often degenerates to effectively testing implementation details of the saga, vs the final result.
So, I really don't think the "testability" concern is as clear-cut as some people argue.
On the flip side, thunks can do some things that sagas cannot:
- Thunks can synchronously execute logic, including
getState(), and continue to do more work immediately
- Thunks can return a value such as a promise to the site that dispatched them, allowing you to do things like executing component logic after a fetch has completed
- Thunks do not require creating extra actions to act as "signals" just to kick off a saga in the background, so they're simpler that way
- The thunk middleware is only 15-ish lines long, while the saga middleware is 5.3K min+gz.
redux-cofxappears to be less hefty, at 2.8K min+gz, but both of those do add additional size.
redux-observableis only 1.5K, but also requires whatever portions of RxJS you're using.
redux-cofx require understanding of both generator syntax and the specific library APIs, while
redux-observables requires understanding observables and the RxJS API. Those add considerable mental overhead.
I think a lot of the complaints about thunks are due to concerns about nested promise chains, and I'll fully agree that those are not particularly easy to read or maintainable. However,
async/await syntax makes that a lot cleaner to work with.
Again, I'm not saying thunks are the only solution you should use, or that sagas/observables/generators are bad and you should never use. All I'm saying is that thunks are a perfectly acceptable tool for a variety of use cases, and that they make a suitable default approach for folks to use for async logic with Redux.
This is a post in the Blogged Answers series. Other posts in this series:
- Aug 02, 2017 - Blogged Answers: Webpack HMR vs React-Hot-Loader
- Dec 18, 2017 - Blogged Answers: Resources for Learning React
- Dec 18, 2017 - Blogged Answers: Resources for Learning Redux
- Mar 29, 2018 - Blogged Answers: Redux - Not Dead Yet!
- Jan 19, 2019 - Blogged Answers: Debugging Tips
- Jul 10, 2019 - Blogged Answers: Thoughts on React Hooks, Redux, and Separation of Concerns
- Nov 26, 2019 - Blogged Answers: Learning and Using TypeScript as an App Dev and a Library Maintainer
- Jan 01, 2020 - Blogged Answers: A Comparison of Redux Batching Techniques
- Jan 01, 2020 - Blogged Answers: Reasons to Use Thunks
- Jan 01, 2020 - Blogged Answers: Years in Review, 2018-2019
- Jan 19, 2020 - Blogged Answers: React, Redux, and Context Behavior