-
Notifications
You must be signed in to change notification settings - Fork 12k
refactor(@angular/cli): create a memoize decorator
#22881
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
Conversation
|
@dgp1130, any concern about enabling |
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.
@dgp1130, any concern about enabling
experimentalDecoratorsinternally ?
Internally, we don't allow "new" decorators (that is, ones that we aren't already invested in and required major frameworks like Angular or LitElement). I believe the intent is to avoid creating unnecessary migration problems if and when JS decorators ever land in the spec and TS decorators go away. That said, I'm ok with this particular decorator for a couple reasons:
- This is a third party package and not developed directly in google3.
- Typically unconformant code like this often becomes the problem of infrastructure teams which need to support it, but user code of frameworks with decorators (ex. Angular apps) will be a much bigger issue than our one decorator here and being a third-party codebase, fixing it remains our problem anyways. So we aren't adding much burden to anyone else.
- This decorator is not exposed as a public API and is unlikely to grow significantly beyond the usage in this PR.
memoizeis a pretty commonly used concept (particularly in web frontends thanks to React), so I think it is very unlikely that this won't be possible in a future JS decorator.- Even if we do need to go back on this and remove the decorator, I don't expect it would be that much effort to do so.
As a result, I think this particular decorator is probably ok. However, don't take this as carte blanche to add a bunch of decorators to the CLI. 😉
| } | ||
|
|
||
| private _schematicCollections: Set<string> | undefined; | ||
| @memoize() |
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.
Does @memoize() need to be a function? It looks like we never parameterize it. Could we rewrite it to just do @memoize?
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.
Updated.
| return { | ||
| ...descriptor, | ||
| [descriptorPropertyName]: function (this: unknown, ...args: unknown[]) { | ||
| const key = JSON.stringify(args); |
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 requires that arguments are serializable to JSON. A quick look at the algorithm's explanation (unofficial) shows that non-serializable arguments (such as a function) return 'null', which could lead to surprising bugs. Ideally we wouldn't have a serializability requirement and I think that's possible with some kind of recursive Map<first param, Map<second param, Map<..., result>>>.
That would be pretty complicated, so I think we should at least document the serializability requirement and possibly check the arguments to make sure they are serializable. For example, we could JSON.stringify() each arg and make sure the result is only 'null' if the actual value is null. There might be other cases, and getters might still cause confusion, but that would probably be "good enough" safety for what we're doing here.
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.
Updated. PTAL
| } | ||
|
|
||
| /** Method to check if the values are JSON serializable */ | ||
| function isJSONSerializable(values: unknown[]): boolean { |
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 seems quite involved, I'm wondering if we can simplify this by just checking if each argument serializes independently? For example:
function isJSONSerializable(values: unknown[]): boolean {
for (const value of values) {
if (JSON.stringify(value) === undefined) {
return false;
}
}
return true;
}This probably isn't as thorough as your implementation, since it won't catch deeper issues ([0, NaN, 1], {foo: () => {}}, ...), but I think this would be good enough to catch mistakes like memoizing a callback function.
One other nit: If the function is checking multiple values, it should probably be named areJSONSerializable(). Alternatively it could just accept one parameter and @memoize could be responsible for iterating over the arguments and checking each one.
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.
There are also some edge cases which likely can be encountered. For example passing a Set as argument which isn't take unlikely.
JSON.stringify(new Set([1]))
'{}'
JSON.stringify({})
'{}'
With this change we clean up repeated caching code by creating a `memoize` decorator that can be used on get accessors and methods.
|
This issue has been automatically locked due to inactivity. Read more about our automatic conversation locking policy. This action has been performed automatically by a bot. |
With this change we clean up repeated caching code by creating a
memoizedecorator that can be used on get accessors and methods.