What to Do When You Want to Affect the Order of Subscription Callbacks in ReSwift
This blog post was originally meant as a reply to the ReSwift issue “Any way to control the priority of subscription callbacks?” – If you find asking yourself this question, this should help.
This implementation detail is hidden by design. If you find yourself wanting to affect the order of subscription callbacks, there’s a concept of a sequence waiting to be extracted somehow. Or, the other way around: if you want to affect the order, your design is wonky.
ReSwift does not guarantee any subscription callback order. Since ReSwift uses an unordered Set
as its subscription store, there’s no reliable way to know when a subscription is invoked. So the short answer is “No”.
These are the ways out of such a situation:
- Use object delegation. Have 1 “wrapper” subscriber forward the event to multiple other objects which are themselves not subscribers; the wrapper subscriber encapsulates the sequence. The sequence thus becomes a thing in your application code.
- Dispatch sequences of event (e.g.
enum
cases) with multiple subscribers set up so that each listens to a different substate value. The sequence thus becomes part of your state representation. - Use Middlewares if you want a side effect to happen before/after an action. The sequence thus is represented as a decoration for an action.
As an example, let’s discuss the handling of network requests.
Wrapper approach
Encapsulate the sequence in Subscriber
and delegate to the Network service
first, then to the Presenter
afterwards:
+-----------+
| State |
+-----+-----+
|
|
v
+-----+-----+
| Subscriber|
+-----+-----+
|
+----------------+
1) | | 2)
+-----v-----+ +------v------+
| Network | | Presenter |
| service | | |
+-----------+ +-------------+
Network service
triggers a request;Presenter
shows the result or progress.Subscriber
is the wrapper object that receives state updates to trigger the request. It knows/owns the other two services.
Multiple states
Encapsulate the sequence in the state itself. A NetworkRequest
enum with multiple states does this for you:
enum NetworkRequestState {
case starting(URL)
case pending(RequestInfo) // e.g. grouping host, duration, ... for debugging
case completed(Response) // e.g. JSON response or a decoded object
}
Then you can have multiple subscribers waiting for the value to change.
- Change the state to
.starting(requestURL)
, and - have a subscriber wait for
.starting
cases, then fire a network request, and dispatch an action to mark the request as pending. - Have another subscriber display a loading indicator as long as
if case .pending(_) = theCurrentState
matches. - Dispatch a completion action when the request finishes, changing the substate to
.completed(theResponse)
, and - have a third subscriber process results.
Which object should dispatches the completion action, though? Could be a thunk, a Promise, or an async callback block of the network request service.
Middleware
The order of Middleware is set by the user, so you can influence this directly. Listen for certain actions in your Middleware, e.g. StartingNetworkRequest(URL)
, then fire a network request from there and handle the callbacks.
You can also discard all other actions in the meantime using the same middleware, or buffer actions, waiting for the request to complete. The Middleware may use internal caches of actions from request start to completion and to debounce repetetive dispatches, for example.
This could be a helpful heuristic:
As a rule of thumb, I use subscribers to change my views and the middleware to apply other kind of side effects.
—Daniel Martín