-
-
Notifications
You must be signed in to change notification settings - Fork 15.2k
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
avoid useless calls to listeners #1626
Conversation
Are there any edge cases this is breaking? In particular I’m concerned that we shouldn’t break this invariant:
This should be true for nested dispatches too. I always find it hard to wrap my mind around this piece of code so maybe if you could add more test cases verifying the behavior we have in the docs, we can take this. |
They only edge case I can think of, is when a subscriberer unsubscribes another subscriber. A, B, and C subscribe to the store in this very same order.
My personal opinion is that this might be more of a feature than of an issue. What I'm seeing in some tests I'm doing is that with this modification I can easily save up to 10% of the processing time Certainly the proposed changes break what stated in the quote you reported! What do you think about the above example? |
On the other hand, everything works correctly in case a subscriber unsubscribe itself... |
...btw, adding a final call to a listener just before it gets unsubscribed (somewhere around here) would be enough to keep the current behaviour unchanged and still granting that
|
Sure, want to give it a try? |
Ok, this is how it would look like. I think the documentation should also be updated to let people know about the one last call to listeners within unsubscribe (which could otherwise easily lead to infinite loops...). |
There's probably another way to solve the same problem differently: remove unsubscribed listener only at the end of the first notification cycle that completes, somewhere around here, but this requires to modify a little the logic about removing listeners... I'll get back to this soon! |
@@ -115,6 +116,8 @@ export default function createStore(reducer, initialState, enhancer) { | |||
|
|||
isSubscribed = false | |||
|
|||
listener() |
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.
Wait, why are you calling the listener on unsubscribe? That's unexpected behavior.
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.
@timdorr what do you mean with unexpected behaviour?
This is not different from what is already happening: the listener is probably being called with a state which was already notified to him and, as you pointed out this is not a problem at all!
But with the other changes in place, this final call is needed to ensure that that listener is always notified with all state changes happened before its unsubscription.
It's not unexpexted behaviour at all ;-)
Edit: the only edge case could be when a listener is registered and soon after unregistered before any state change could happen. In this case I agree with you: the listener is being called even if the state did not change.
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.
This listener call is entirely unrelated to any state change. It can only be called outside of any dispatch (this is now enforced via #1569). It is impossible to have a state change where the listener is not called.
This is basically creating exactly the thing this PR looks to avoid: an unnecessary listener call.
I'm 👎 on this. The listener can choose to ignore notifications if it wants (most likely by checking the ref. equality of the state). I'd rather have a guarantee of giving me all notifications, than have some sort of different behavior because of listeners added outside of my control. |
I know the proposal is not perfect and I'm trying to improve it. I think the real point here is that I'm trying to lower the computational complexity of this piece of code, not just saving a few function calls. When the number of listener is high and many of them are trying to change the state in response to similar changes, things could get really bad... |
As promised with this previous comment I've made a new proposal: please see #1867. I'm happy to discuss about it. |
OK, let's keep this in one PR. #1867 it is. |
Avoid useless calls to listeners when they've already been notified by the deepest dispatch call.
Dispatching an action from a listener while responding to a state change starts a recursive call to dispatch. This pauses the loop currently used to notify all listeners and starts a new one at a deeper nested level.
When resuming back to higher levels, all the listeners have already been notified and the paused loop doesn't actually need to be completed.
As an example, consider the following code:
Here a number of listeners subscribe to the store and the first three of them trigger an increment action only in case the current value for the state is equal to their
id
.The following is the log I get with the current code:
(I've added by hand the recursion level on the leftmost column)
With the proposed modification, the output would be only the following one:
The idea is to prune the exploration of the recursive dispatch calls and stop notifying once the deepest notification loop completes. After this point in time each listener was already notified and it does not need to check the status on the same chain of actions.
Please let me know what do you think about this schema and do not hesitate to start a discussion on it.