-
Notifications
You must be signed in to change notification settings - Fork 1
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
can.Map.attr() returns stale value for a "compute" type attr immediately after updating under certain conditions #45
Comments
@UselessPickles let me know if you want to pair on this; otherwise, it's not super high priority for us right now. |
@justinbmeyer I stepped through the code for a while, and started getting lost in the setter/getter logic for map attributes that are "computed". When trying to read the attr immediately after setting it, I get into compute.get, where it ends up returning
If I don't have a change listener bound to I noticed that Is the solution as simple as updating the above get() method to return How confident are you on unit test coverage of computes? Should I just try making this change and running unit tests? Any other insight into what the correct solution may be? What is the purpose of this.value vs this.lastSetValue? Is it intentional that this.value is not updated until the batch completes? |
Clarification: the implementation of this._get for compute that uses this.lastSetValue.get() is specifically a special implementation of _get provided by _setupAsyncCompute. So the solution is not as simple as I suggested, because this.lastSetValue exists only for "async" computes. I could hackishly check for this.lastSetValue and use it if available, but that's getting ugly and even less clear as to whether it is correct. Again, this is getting deep into unfamiliar territory for me. Why is the attr's compute setup as an async compute in this situation? what is the purpose? etc... |
Looking at the implementation of _setupAsyncCompute has me even more confused now. When does this.value ever get set for async computes?
|
Maybe the correct solution is to call this._get() from inside get(), rather than directly accessing this.value? The default implementation of _get simply returns this.value, so the behavior is unchanged for "normal" computes, but for various special computes with overridden _get methods, it will correctly get the value. Are there situations where we specifically do NOT want to call _get() and trust that this.value is a correct/cached value?
|
I think I'm getting closer. I stepped through what happens when I set the value. I end up in the proto_compute code here:
Refer back to the _setupAsyncCompute method:
So... what is the correct solution? Should the asyncCompute set this._setUpdates = false, or should it explicitly set this.value inside of it's custom _set implementation? |
I confirmed that can v3 does NOT have this same bug. I hope that someone can use what I have reported to compare v2 code to v3 code and determine whether the v2 code can easily be updated to get rid of this bug. |
After some more experimentation, looking at code, etc., I have come to the conclusion that I am completely lost. The code for v3 still has the _setUpdates flag that on the surface appears to be a lie, but v3 does not have this bug, so it must not mean what I think it means. Can someone that was involved with the design/development of compute code, etc., look at this? |
@UselessPickles I can help though I'm pretty busy this week giving a training. Is there a time Monday we could pair on it? |
FYI; There's a good chance the root cause for your bug is not to be found within async computes and/or computed I ran into this exact same problem with stale values coming from a nested compute in some of my own code recently and it does not match the above conditions. Mine was a case of the Indirectly dependent, because one would pass through several layers of computed I wish I could produce a reduced test-case of my own occurence for you, but the code in question was a massive piece of logic related to implementing a faceted search and the moment I start decoupling, the bug resolves itself. As it stands, all I can do is inform you and hope that it helps assist in tracking down a cause. I sincerely hope this isn't another case or side-effect of nested computes being evaluated in wrong order. Those issues seemed very nasty. Regardless; good hunting! |
@UselessPickles commented on Wed May 31 2017
Discovered in v2.3.27
If I have a can.Map with two "compute" properties:
AND I have created a compute from one of the fields in the map:
var valueComputePassedToAnotherControlFor2WayBinding = dataModel.compute("value");
AND I have bound a change listener to that compute:
valueComputePassedToAnotherControlFor2WayBinding.bind("change", function() {});
AND I change the value of that field in response to the other field changing, then immediately read the value of the changed attribute, I get the old (stale) value:
This unexpected behavior seems to be triggered by a very particular combination of factors. For example, if I change EITHER property definition to not be "compute", then it works properly. Or, if I simply don't bind a change listener to my
valueComputePassedToAnotherControlFor2WayBinding
, then it also works as expected.This is a complicated situation that is best demonstrated, rather than attempting to explain fully in words. Here is a jsfiddle demonstrating the problem, with several comments pointing out aspects of the situation that seem to trigger the unexpected behavior:
http://jsfiddle.net/n491v2xq/4/
The text was updated successfully, but these errors were encountered: