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

cleans up can.Compute.read and adds template-obserbable promises #1540

Merged
merged 2 commits into from Mar 24, 2015

Conversation

Projects
None yet
3 participants
@justinbmeyer
Contributor

justinbmeyer commented Mar 22, 2015

For #179, this makes promises observable when read by can.Compute.read.

Promises in the template.

This powers promises in the template like:

{{#if promise.isPending}}<span class='pending'></span>{{/if}}
{{#if promise.isRejected}}<span class='rejected'>{{promise.reason.message}}</span>{{/if}}
{{#if promise.isResolved}}<span class='resolved'>{{promise.value.message}}</span>{{/if}}

This should work with any promise that passes can.isPromise. Currently, only objects with .then and .pipe are considered promises. Users can overwrite this to be more permissive.

The following properties on a promise are available:

  • isPending
  • isResolved
  • isRejected
  • value - the resolved value of the promise, only available if isResolved is true.
  • reason - the rejected value, only available if isRejected is true
  • state - "pending", "resolved", or "rejected"

This only makes promises observable in a template. If you were to use a promise in a compute, it will not re-evaluate the compute when the promise changes. However, with async getters and setters, you can wire up changes in the observable state to changes in a value yourself.

Changes to can.Compute.read

can.Compute.read is moved into its own file: compute/read.js. It also is designed to be a bit more extensible. The read function uses a list of valueReaders and propertyReaders to read the properties from parent.

valueReaders are used for things like functions and computes, which do not have a property to read.

propertyReaders are used to read properties on an object, can.Map and now promise.

This change allows us to provide multiple ways to translate the DOT operator into specific ways of reading values for the template.

In the future, I think we should make can.compute(parent, "foo.bar") use can.Compute.read. This would allow:

var value = can.compute(map,"promise.value.first.name")

where map's "promise" attribute is a promise that returns another map that has a "first" attribute which is a map with a "name" attribute.

@justinbmeyer justinbmeyer added this to the 2.3.0 milestone Mar 22, 2015

@rjgotten

This comment has been minimized.

Show comment
Hide comment
@rjgotten

rjgotten Mar 23, 2015

Out of curiosity:
How did you solve the use case where you have a pipeline of then calls, where any of those calls are susceptible to using computed properties?

In such a case the chain of promises could, at any point in the chain, need partial re-computation and re-evaluation. You'd then only want to recompute the chain moving forward from the point where a change occurs in a depending computed property, because in general it could be unsafe or extremely wasteful to recompute the entire chain. (In particular when the chain involves one or more XmlHttpRequests taking place.)

rjgotten commented Mar 23, 2015

Out of curiosity:
How did you solve the use case where you have a pipeline of then calls, where any of those calls are susceptible to using computed properties?

In such a case the chain of promises could, at any point in the chain, need partial re-computation and re-evaluation. You'd then only want to recompute the chain moving forward from the point where a change occurs in a depending computed property, because in general it could be unsafe or extremely wasteful to recompute the entire chain. (In particular when the chain involves one or more XmlHttpRequests taking place.)

@justinbmeyer

This comment has been minimized.

Show comment
Hide comment
@justinbmeyer

justinbmeyer Mar 23, 2015

Contributor

@rjgotten can.compute only calls the "computing" function when one of its source values change.

Lets say you did something like:

{{compute1.promise.value.compute2.promise.value}}

Where your data looked like:

var params1 = new can.Map({});
var params2 = new can.Map({message: "Hello There"});

var getData = function(params){
  return new Promise(function(resolve){
    setTimeout(function(){
       var compute = can.compute(function(){
         return {promise: getMessage(params2.attr()) };
       });
       resolve({compute2: compute2});
    },100);
  })
};

var getMessage = function(attrs){
  return new Promise(function(resolve){
    resolve(attrs.message);
  })
};

var dataToTemplate: {
  compute1: can.compute(function(){
    return {promise: getData(params1.attr())}
  })
}

If I were to change params2's message:

params2.attr("message", "Goodbye")

Only getMessage will be called again. getData will not.

The reason is that despite compute1.promise.value.compute2.promise.value having to be read, when compute1 is re-read, it will return the same object in memory until one of its source observables has changed (in this case params1). Because params1 has not changed, it uses the same promise originally returned by getData.

So, I think the issue you are asking about is solved b/c can.compute kicks ass.

Contributor

justinbmeyer commented Mar 23, 2015

@rjgotten can.compute only calls the "computing" function when one of its source values change.

Lets say you did something like:

{{compute1.promise.value.compute2.promise.value}}

Where your data looked like:

var params1 = new can.Map({});
var params2 = new can.Map({message: "Hello There"});

var getData = function(params){
  return new Promise(function(resolve){
    setTimeout(function(){
       var compute = can.compute(function(){
         return {promise: getMessage(params2.attr()) };
       });
       resolve({compute2: compute2});
    },100);
  })
};

var getMessage = function(attrs){
  return new Promise(function(resolve){
    resolve(attrs.message);
  })
};

var dataToTemplate: {
  compute1: can.compute(function(){
    return {promise: getData(params1.attr())}
  })
}

If I were to change params2's message:

params2.attr("message", "Goodbye")

Only getMessage will be called again. getData will not.

The reason is that despite compute1.promise.value.compute2.promise.value having to be read, when compute1 is re-read, it will return the same object in memory until one of its source observables has changed (in this case params1). Because params1 has not changed, it uses the same promise originally returned by getData.

So, I think the issue you are asking about is solved b/c can.compute kicks ass.

@rjgotten

This comment has been minimized.

Show comment
Hide comment
@rjgotten

rjgotten Mar 23, 2015

Ah! So it's the underlying cache retained while a binding is attached that does the magic here.
Nice!

(And yes; can.compute does kick ass. Well; as of CanJS 2.x anyway. The 1.x version was a bit of a letdown with the hard dependency on the then can.Observe. ;-) )

rjgotten commented Mar 23, 2015

Ah! So it's the underlying cache retained while a binding is attached that does the magic here.
Nice!

(And yes; can.compute does kick ass. Well; as of CanJS 2.x anyway. The 1.x version was a bit of a letdown with the hard dependency on the then can.Observe. ;-) )

@daffl daffl modified the milestones: 2.2.1, 2.3.0 Mar 24, 2015

daffl added a commit that referenced this pull request Mar 24, 2015

Merge pull request #1540 from bitovi/observable-promises-179
cleans up can.Compute.read and adds template-obserbable promises

@daffl daffl merged commit b5944d6 into master Mar 24, 2015

2 checks passed

continuous-integration/travis-ci/pr The Travis CI build passed
Details
continuous-integration/travis-ci/push The Travis CI build passed
Details

@daffl daffl deleted the observable-promises-179 branch Mar 24, 2015

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment