@lazy decorator
Closed this issue · 8 comments
As shown in #342, we can create a decorator that can initialize things lazily within set
or get
, so ``@lazy` could be implemented in userland.
Maybe it would be convenient to have a built-in @lazy
decorator that makes the initializer
lazy (on first get
, unless set
has already been triggered).
Here's problem with Custom Elements and a solution with @lazy
:
The following has a runtime error, the subclass can not override the superclass property because this.attachShadow
always throws if it is called more than once (and element can only have one ShadowRoot):
class Component extends HTMLElement {
renderRoot = this.attachShadow({mode: 'open'})
connectedCallback() {
console.log(this.renderRoot)
}
// ...
}
class MyClass extends Component {
renderRoot = this.attachShadow({mode: 'closed'}) // runtime error, super class already called this.attachShadow, an unwanted side-effect.
// ...
}
Solved with @lazy
:
class Component extends HTMLElement {
@lazy renderRoot = this.attachShadow({mode: 'open'})
connectedCallback() {
console.log(this.renderRoot)
}
// ...
}
class MyClass extends Component {
// also use @lazy here to allow possible subclasses of MyClass to override renderRoot
@lazy renderRoot = this.attachShadow({mode: 'closed'}) // no error
// ...
}
Now it works, because when connectedCallback
finally runs at some time after construction, it will read the subclass renderRoot
property, at which point the subclass initializer will run, and therefore this.attachShadow
will have been called only once using the subclass' version, and the act of "overriding" a super class property was successful.
The unfortunate thing with class fields (and no use of decorators for help) is that they do not "override" superclass fields of the same name.
This proposal does not introduce ways for subclasses to override/change the behavior of superclasses, which this would require if decorating accessors/fields. As such, this is out of scope of this proposal, so I'm closing this issue.
I have another use case in that I want to defer the expensive computation until the field is used for the first time. I found it's impossible in the latest decorator spec.
@Jack-Works it is not impossible if you use the accessor
keyword
it's impossible whether I use accessors or not
@Jack-Works if you use accessor
, you can decorate the auto-accessor and add a getter which computes the value the first time it is accessed. Just don’t return an initializer, instead defer the computation via the getter.
@Jack-Works if you use
accessor
, you can decorate the auto-accessor and add a getter which computes the value the first time it is accessed. Just don’t return an initializer, instead defer the computation via the getter.
how? I don't see anything related in the first or second argument when the decorator function gets called.
to be clear I mean
@lazy accessor x = new HeavyObject()
not
@lazy accessor x = () => new HeavyObject()
Ah, understood. Yes that would unfortunately be impossible. The inability to capture initializers during decoration was a hard requirement from engines, who believed doing so would make initialization impossible to optimize.