Abramov's goal - and everyone's ideally - is simply to encapsulate complexity (and async calls) in the place where it's most appropriate.
Where's the best place to do that in the standard Redux dataflow? How about:
- Reducers? No way. They should be pure functions with no side-effects. Updating the store is serious, complicated business. Don't contaminate it.
- Dumb View Components? Definitely No. They have one concern: presentation and user-interaction, and should be as simple as possible.
- Container Components? Possible, but sub-optimal. It makes sense in that the container is a place where we encapsulate some view related complexity and interact with the store, but:
- Containers do need to be more complex than dumb components, but it's still a single responsibility: providing bindings between view and state/store. Your async logic is a whole separate concern from that.
- By placing it in a container, you'd be locking your async logic into a single context, for a single view/route. Bad idea. Ideally it's all reusable, and totally decoupled.
- Some other Service Module? Bad idea: you'd need to inject access to the store, which is a maintainability/testability nightmare. Better to go with the grain of Redux and access the store only using the APIs/models provided.
- Actions and the Middlewares that interpret them? Why not?! For starters, it's the only major option we have left. :-) More logically, the action system is decoupled execution logic that you can use from anywhere. It's got access to the store and can dispatch more actions. It has a single responsibility which is to organize the flow of control and data around the application, and most async fits right into that.
- What about the Action Creators? Why not just do async in there, instead of in the actions themselves, and in Middleware?
- First and most important, the creators don't have access to the store, as middleware does. That means you can't dispatch new contingent actions, can't read from the store to compose your async, etc.
- So, keep complexity in a place that's complex of necessity, and keep everything else simple. The creators can then be simple, relatively pure functions that are easy to test.