Blogged Answers: Reasons to Use Thunks

This is a post in the Blogged Answers series.

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 dispatch() and 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-cofx appears to be less hefty, at 2.8K min+gz, but both of those do add additional size. redux-observable is only 1.5K, but also requires whatever portions of RxJS you're using.

In addition, redux-saga and 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:

Author Avatar

Mark Erikson

Collector of interesting links, answerer of questions