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
Change side effect dispatch model #9
Conversation
*/ | ||
|
||
const cleanEffectQueue = () => { | ||
const values = effectQueue.map(dispatch); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As explained:
- It should definitely be banned to dispatch an action here, only thunks should be allowed otherwise we would allow user to cascade actions.
- We can't afford reliance on user's
redux-thunk
as peer dependency
The PR is much appreciated! I left few comments, these are just cosmetic things, mostly related to code style and let's discuss few conceptual problems/ideas here in the discussion. Let me first ask a question, is the "all effects have been dispatched" hook the only purpose of this PR? I definitely like the idea of providing the Now few problems: Only thunks are allowed as side effectsI explained this in the comment already, we should not allow user to Now you might protest because I am saying that only async thunks are accepted yet we are not deferring the action execution in the current implementation but you are doing that in your PR meaning that it's better to do it your way. However, I believe that we should not explicitly defer the action execution. Dispatched actions within effect should be naturally asynchronous otherwise it's action chaining which is wrong, user's responsibility is to met this condition, our responsibility is to verify that they are not doing that. Action dispatched via side effect should not allow yielding another effects which dispatch another actionsIn the current implementation we are ensuring the exact opposite. Few days ago I realized that this approach is actually wrong. I always kept advocating the idea that as long as the action is dispatched asynchronously it's not cascading event and it's still a valid point but the argument about single interaction entry point is much more stronger. Therefore this is not ideal: function* reducer(appState, action) {
if (action === 'FOO') {
yield dispatch => asyncWork().then(() => dispatch('BAR'));
} else if (action === 'BAR') {
yield dispatch => asyncWork().then(() => dispatch('BAZ'));
}
return appState;
} We should instead enforce doing this: function* reducer(appState, action) {
if (action === 'FOO') {
yield dispatch => {
asyncWork()
.then(() => dispatch('BAR'))
.then(asyncWork)
.then(() => dispatch('BAZ'));
}
}
return appState;
} We are obviously giving up some flexibility but this approach will make the code much more easier to reason about. Because of this invariant we'll proably have to keep the We can use Promise chain + middlewaresIf the answer for my question is yes, then I believe we can implement it with less intrusive implementation which will use promise chain and middleware. Bottom line: we can definitely use some parts of your code but let me first do some proof of concept with less intrusive implementation. |
There were a couple goals of this PR: reduce surface area (only wrap a single function), remove I think just because we think a user shouldn't do something, that doesn't mean we should enforce it. There might be some who argue side effects in a reducer should never be done, but because it can be done we can experiment with different styles. This applies to two things. First is the anything-dispatching. I don't think I completely understand the technical limitation keeping us from allowing it. Technically all actions (whether thunked or not) are dispatched, and what is the real benefit of I mainly want this because I have some custom middleware which responds to promises, so writing Second, I completely agree that effects shouldn't have effects, but I don't think we should enforce that. Once an action is dispatched it would be weird to (externally or internally) keep some metadata on it disallowing further effect propagation. Once an effect is dispatched it should behave like any other action. In some systems this also might be useful. Finally, I'll have to see your promise chain implementation. My initial reaction is that I'm not sure I like it. Adding promises kinda removes the purity of the implementation. I am curious about the "+ middleware" part though. |
Excellent! I agree with:
Yes and No. Just keep in mind that even when using
All I wanted to say is that either
Agreed, I might have not expressed myself clear enough. It should not be allowed to dispatch sync action in effect. Promise is by nature async therefore it makes sense to accept either thunk or
To advocate this it would require probably whole blog post, for starters, it may be useful to at least log some
Here it is |
Ok, I get it now. However, I still believe in dispatching whatever is yielded for the middleware reason I stated above. A warning/error when yielding a plain object is totally understandable though. I think development warnings are a good idea to solve some of the pattern breaking issues (plain object dispatch & effect effects) but I don't think we should limit technical capabilities. |
202efae
to
16765c7
Compare
Ok, the code is ready to merge except for Points of contention that are still included in this PR are:
I'm willing to put error messages in development for both of these, however I think it best if we leave the capability to do these things even if they aren't best practice (for extensibility purposes). Finally, to tie a nice bow on this PR do you want to rename |
Ok, added the warnings for anti patterns for discussion. |
e3b5f0d
to
6f03c23
Compare
6f03c23
to
da3b284
Compare
Thanks @calebmer, will merge the code in temp branch. There are few things I want to polish. For example I would like to get rid of |
Ok, sounds great 😊 |
Hello @calebmer, sorry it took me so long but I would like to revive the discussion. We've had some issues trying to solve how everything fits into a larger application and we would really like to see this PR merged, however I'd rather avoid over-engineering stuff. There have also been many changes in the overall architecture decisions which come up from redux-elm I have incorporated some of your ideas and tried to implement something simple and here's the starting point 875ddc6 Could you please have a look whether this kind of change makes sense for you? We'd need somehow expose |
Hey, long time no see 😊 I've actually come full circle too, especially after seeing this proposal. I've moved on to another project since this PR, but I'm looking for a chance to try it again soon. That said, it took me a bit to get back in the mindset. The code in 875ddc6 looks great, much cleaner than what this PR ended up becoming. As for So at this point, should we start thinking about closing this PR in favor of the |
If there's any other way I could help, please let me know 😊 |
Continuations with Effect Handlers is a nice idea, yet I am afraid that major drawback of the solution is that propagation of side effects is not explicit. It works like Thanks for your help @calebmer closing in favor of |
I didn't have much time today to flesh out my ideas, but here is a rough first draft for some of my ideas in #8. I decided to push now (with all tests failing☺️ ) as the changes are pretty much how I envisioned them, so we can use them as a discussion point. What's left to do is some admin stuff:
defer
/clearDefer
utilities (just asetTimeout(callback, 0)
abstraction)