Closed justinbmeyer closed 6 years ago
So the problem is really that we want to see if we have a computedInitializer
and created the computedObj
for our specific instance if it hasn't already been created.
WeakSets might provide a better way of doing this. Given the instance and the computedDefinitions
, we should be able to do what we need.
Only problem w/ weakSets is that it's not as easy to trace.
Goals:
[ ] - Write how to make a decorator in can-observe
's docs: https://canjs.github.io/next/doc/can-observe.html
[ ] - Make decorators - resolver - async getter.
@resolver
fullNameChangeCount({ resolve, listenTo}){
var count = resolve(0);
listentTo("fullName", () => {
resolve(++count);
});
}
@asyncish
todos(resolve) {
this.todosPromise.then(resolve);
}
make sure inheritance works
on inheritance ...
// When can we do this? On new SuperHero()
SuperHero.prototype.computedDefinitions = Object.create( Person.prototype.computedDefinitions )
I think there should be some lower-level resolver
and async
, etc. functions. The signature would be resolver(fn) -> fn
. Then the decorators could use these under the hood adding whatever decorator stuff is necessary.
The use-case is to have this sort of function in non-class based APIs. For example can-element will (likely) not be class based. Since decorators don't work on object literal methods we would need a functional composition method, something like:
computed: {
fullName: resolver(function({resolve}){
// ...
})
}
@matthewp https://github.com/canjs/can-observe/pull/40/commits/6e756941efd9f5685deb26d1d9b57d413a64aacd Oh, I misread your comment. I added the resolver decorator, but not lower level functions.
There is an addComputedPropertyDefinition
method that these are using under the hood. The following is literally all the code (other than dev-only error checks) for the @resolver
decorator.
addComputedPropertyDefinition(target, key, function(instance, property) {
return new ResolverObservable(method, instance);
});
The asyncGetter
decorator is a little more complex (because it accepts a getter and a method, and handles them a little differently, and to change the call signature on the method).
For methods:
addComputedPropertyDefinition(target, key, function(instance, property) {
var observable = new AsyncObservable(function() {
method.call(this, observable.resolve);
}, instance, config.default);
return observable;
});
For getters:
addComputedPropertyDefinition(target, key, function(instance, property) {
var observable = new AsyncObservable(function() {
var promise = getter.call(this);
if (promise !== undefined) {
if (promise !== null && typeof promise.then === "function") {
promise.then(observable.resolve);
}
//!steal-remove-start
else {
throw new Error("asyncGetter: getters must return undefined or a promise.");
}
//!steal-remove-end
}
}, instance, config.default);
return observable;
});
I could certainly wrap these so they are more easily usable without decorators, though the syntaxes would need to be called with the instance or prototype (depending on if you have a singleton or a class), teh key to store it under, and the function to run.
I want to make it possible to easily mixin behaviors like async computes and the new resolver observables:
We need to be able to make it easy to mixin these observables in a performant way similar to
can-define
.can-define
is able to lazily construct observables for instances only when the instance property is actually bound, read or set.As this is beyond what Object.defineProperty enables (only get and set, no binding lifecycle hooks), we need to make something ourselves.
Here's some background on how
can-define
works.can-define
keeps adefinitions
object on the prototype with acomputedInitializers
object:A
computedInitializer
object looks like a object of property names to functions that return acomputeObj
:can-define
also creates a lazily-defined_computed
property on the prototype (~
denotes lazy). When _computed is read for the first time, it will create create an object of lazily defined getters that return thecomputeObj
from above:Putting this another way, when an instance is created, it does some partial work to make a
_computed
object on the instance that lazily creates thecomputedObj
. The partial work is really just making surefullName
above will be called with the rightthis
.Finally,
can-define
just looks in_computed
when binding and unbinding.Making this useful
We want nice APIs that people can hook into. I'll start with rough proposals that work backwards in terms of performance:
installing an already instantiated observable value as a property on an instance
lazily installing observable property behavior on an instance
lazily installing observable property behavior on all instances via the prototype