-
Notifications
You must be signed in to change notification settings - Fork 88
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Is the return value from Store.Dispatch useful for anything? #48
Comments
More info: I came across this while trying to implement “listener middleware”, according to Alex Reardon's post The ‘middleware listener’ pattern: better asynchronous actions in Redux. To me this seems like a great way to implement async operations in Redux, because all async (and other side effect) operations are clearly separated from all other parts of the app. Basically, you create a “listener middleware” that first dispatches the action down the pipeline (by calling For example, a click on a sign-in button will send a simple action After calling the listeners, I have to return something from the middleware in order to fulfill the "interface", but it doesn't seem to matter if I return Here's a rough first draft of the listener middleware I created: public class ListenerMiddleware
{
public delegate Task Listener(IAction action, RootState state, Dispatcher dispatcher);
private readonly Listener[] listeners;
public ListenerMiddleware(params Listener[] listeners)
{
this.listeners = listeners;
}
public Func<Dispatcher, Dispatcher> CreateMiddleware(IStore<RootState> store)
{
return next => action =>
{
RootState preActionState = store.GetState();
IAction ret = next(action);
foreach (Listener listener in listeners)
{
listener(action, preActionState, store.Dispatch);
}
return ret;
};
}
} |
After some more thought and investigation, allow me to partially answer my own question: The return value from the middlewares “bubbles up” through the middleware stack, and is ultimately accessible as the return value of My question now is, is this return value useful for anything? |
return next(action);
or next(action); return null;
?
@cmeeren the result from middlewares bubbling up is much more important if you are using redux with something like javascript that has no types. If you think of a middleware that might return a promise, you could then see yourself writing some code that looks like store.dispatch(myActionThatHitsPromiseMiddleware).then(...) here it is important that the In the case here, I am not seeing any usefulness yet as the result of a dispatch will always yield an IAction that has no real behavior. I think it would be interesting to attempt to create the middlewares in such a way that the return value from dispatch was generic. It should be possible? But I haven't tried yet @GuillaumeSalles do you think it is possible to implement the Dispatcher in such a way that its return value doesn't have to be IAction? If I wanted my middleware to do something async with a Task or something would it be possible? |
Thanks a lot @cmeeren and @lilasquared for your comments. You definitely put a lot of thoughts on the subject! @lilasquared is totally right about the purpose of the Dispatcher's return value. I tried to make the Dispatcher generic one year ago to dispatch but if I remember correctly the C# type system is a too restrictive to achieve what you want to do. Let's imagine we transform the Dispatcher delegate from this : public delegate IAction Dispatcher(IAction action); to this : public delegate TAction Dispatcher<TAction>(TAction action); The IStore interface becomes IStore<TState,TAction> and it force us to define what kind of TAction can be dispatched at the store creation. It can be hard to imagine this limitation without coding, but feel free to refactor the Store class to see it by yourself. The refactoring can be done really quickly. If you find a better solution to solve this issue, don't hesitate to make a PR. I would be glad to discuss it with you. However, I totally agree that the IAction markup interface is useless. The listeners middleware seems to be an interesting pattern but I have the feeling that this additional indirection can be make difficult to follow the execution flow on a large app. But maybe it's just an impression since I never tried it. I solved the async issue with an extension method on IStore : I doesn't follow the redux philosophy so I did not add it to the nuget package. |
I'm also entirely new at this, but to me it seems like a great pattern, because the logic performing async actions are completely separated from the rest of the code. Say you have a login page. In the view's codebehind, all you have is If this logic was instead kept in the view's codebehind, then you're back to something akin to the MVVM problem of potentially large viewmodels, and furthermore you'd have to duplicate this logic if you make platform-specific UIs. I do agree that the control flow can be a tad bit more obscure, but this hasn't been a problem for me yet; I find that as long as I define the right actions, everything in the “UI” project is more or less just dispatching actions based on UI events, and in the PCL all the control flow logic is in the reducers and the listeners (or other middleware). The listeners frequently have dependencies (API calls, view navigation, dialog messages, etc.) which I inject using DI (I therefore create all my listeners, other middleware and the As a side note, I have in fact made my own Redux implementation inspired by Redux.NET (cmeeren/yarni, maybe I'll put it on Nuget sometime) with a few changes:
Let me know if you spot any glaring mistakes, though it currently works fine and I think I'm not too far off target. |
@cmeeren i will definitely check it out but i would recommend to make the middleware you've bundled with it its own package if you post to nuget simply for the sake of separating concerns. redux does not ship with any middleware at all. If store.dispatch returns void then does that mean you cannot return anything from it? So the case of chaining async actions wouldn't work in the case of the redux-promise-middleware correct? Have either of you used Mediatr? I wonder if these two can work together or if they are too different. |
Thanks, I've considered that and will probably do that if I make it available on Nuget.
Correct. Returning void means you can't chain anything, because there's nothing to chain. This doesn't matter to me, because I use the listener pattern instead of async actions or other similar methods. Having to return things from my middleware just felt like noise.
Never heard of it. |
The action creators logic in the async example are in the PCL projects too. I see Redux (javascript and .NET) as a low level library that allow the users to build higher level extensions to consume it the way they want. I thinks this extensibility is one of the root cause of its popularity in the javascript community. So listeners are definitely is a great opinionated approach to dispatch async actions. Never heard of MediatR but I can see the similarity with listeners and how it can be used to dispatch async actions. Yarni seems great! If I had to release a v3 of Redux.NET (to remove the Rx dependencies), the code would probably be really close. |
Oh, now I see. I hadn't looked closely enough at this example. A quick comparison:
Potential pitfalls/tradeoffs of
Downsides of the current implementation of
In the end, everything's a tradeoff. Right now, I'm leaning towards async listeners. |
@cmeeren @GuillaumeSalles I've been working on this for some time now and I think that I have a pretty good implementation going. I studied the redux source code and ported it almost one for one. The only thing I'm missing right now is the $$observable implemenation - mainly because I haven't used it and I don't know what it does. I am very open to feedback and would be interested to hear what you guys have to think since you both have your own implementations. You can find mine at https://github.com/lilasquared/NRedux or you can install it from nuget https://www.nuget.org/packages/NRedux/0.5.1. |
@cmeeren Great analysis! Just to be clear, I'm not too fond of my implementation of async action creators... (That's why the extension method is not in a nuget package.) I tried to follow the advices of the official documentation (http://redux.js.org/docs/advanced/AsyncActions.html#async-action-creators) like @lilasquared did. var promise = store.Dispatch(Helpers.AddTodoAsync("Use Redux")) as Task; We can't guess the return type of the dispatch without knowing which middleware has been injected in the store. The dispatch async action creator is just a patch I used before with my team. @lilasquared : Your implementation is great ! It's awesome that you even made StoreEnhancer and CombineReducer. The exception to avoid dispatch from a reducer is cool too. I clearly remember the redux javascript code source while going through your code. :) Are you coming from a javascript background? Some comments :
await (Task)(store.Dispatch(Helpers.AddTodoAsync("Maybe")));
Assert.Equal(expectedState, store.GetState()); |
@GuillaumeSalles i have a background in both equally but I am a huge fan of javascript. I pretty much read through the redux code and did my best to port it to C#. I agree that the forced casting for the results of dispatch is a little messy. Since the action creators define which type of action will be created and in a way tell the dispatcher which middleware to use, at design time you kind of do know what the return type should be. It was the only way I could get it to work that seemed like it was not a huge deal. It would be possible to abstract it to a boundActionCreator that would always return a typed task so that you wouldn't have to cast each time its used. I can try to mock up something like that as well. I really appreciate the feedback - I will have to check out the events instead of the listeners - would that limit in any way what the listeners could be? I was trying to keep it as vanilla as possible to resemble the actual redux source. Thanks for the test example for multithreading - i will add one and see how it performs. I think for the unit test I got so excited that it actually worked that i didn't think about making the test more concise. Do you think any of this is translatable to Redux.net? I pulled yours down and started trying to implement some things but I felt like I was basically rewriting the whole thing. I would totally be willing to help you out if you are interested. |
Sure! We can continue to talk about it here #51 |
Originally titled
Middleware - no difference between `return next(action);` or `next(action); return null;`?
. See second comment below for new info.I came across this while implementing a middleware which sent the action off to async "listeners", see the next comment for details.
For simplicity's sake, consider the following middleware:
The app seems to run exactly the same if I replace
return next(action);
withnext(action); return null;
:What is the significance of the return value from the middleware? How is it used in Redux.NET?
The text was updated successfully, but these errors were encountered: