Promises and computed properties

Lately, I’ve found myself trying to use computed properties that derive from a promise. For example, trying to get an user’s facebook data, or just waiting for some asynchronous behavior that’s fired from the property itself (because I don’t want to do it if no one is bound to it). This way I can benefit from caching, lazy loading, etc.

Since ember is already using promises as a core technology, wouldn’t it make sense having something like:

App.User = Ember.Object.extend({
  facebookPicture: function(){
    return new Ember.RSVP.Promise(function(resolve){
      fetchFacebookPicture(this.get('facebookId'), resolve);
    });
  }.property('facebookId')
});

I know we can solve this by using observers and setting properties but seems unidiomatic and I think the asynchronous nature of computed properties do seem to fit promises.

Thoughts?

6 Likes

This would mean that facebookPicture is a promise.

var user = App.User.create({
  facebookId: "1235232332"
}});

user.get('facebookPicture') // -> Promise

Is this intentional?

Also, you’re passing resolve function as a callback to fetchFacebookPicture which would produce a Run Loop not running assert message in testing.

I think there’s nothing wrong with this, but it does mean that you need to use user.get('facebookPicture').then(function(picture) { ... }) to access the value. What could be a good convenience would be to add a helper for this:

{{when facebookPicture}}

This would wrap the promise with a PromiseProxyMixin and then be equivalent to:

{{#if facebookPicture.isFulfilled}}
  {{facebookPicture.content}}
{{/if}}
3 Likes

This looks interesting. Definitely worth exploring.

@aexmachina check this out http://emberjs.jsbin.com/eLiYeboj/10/edit

I created a computed property that takes a callback as a parameter and created a {{when}} helper that allows to show information from the resulting value. The helper is broken. I don’t know how to get it to replace the else value with rendered value from options.fn.

Can you take a look?

Although it sounds very interesting, I think this scenario is a rare one. Often times, you will just set a property on the controller. Fulfill scenario is straightforward, but what about the fail one? Where do you attach fail handler to the promise and keep the syntax simple?

@tarasm I don’t think you can create a bound helpers with Handlebars block syntax.

I guess you would have to trust the resolved promise, otherwise return null, right?

I don’t know for sure. I think its possible, just complicated. I looked at some of the built in Ember Handlebars helpers and they’re pretty complicated.

I guess you would have to trust the resolved promise, otherwise return null, right?

I’m going to experiment with this tomorrow.

You’re right. It’s not possible to register bound block helpers.

I got it working in another way,

{{#if gotFish}}
  You caught {{when gotFish}} fish.
  {{else}}
    You went fishing
{{/if}}

Here is an update JSBin.

I don’t think this technique is useful because promises can not be reused(once they’re resolved, they’re always resolved). Also, there is no way to programmatically handle error cases from the template.

Another way to handle this kind of situation is to encapsulate the promise resolution inside the computed property:

myThing: function(k,v) {
  if (arguments.length > 1) {
    return v;
  }
  var self = this;
  computeSomeValue().then(function(theValue) {
    self.set('myThing', theValue);
  });
}).property()
```

That handles the simpler cases. If you want it to be really bulletproof against multiple invalidations while the promise is still resolving, you'll want to track a little more. The following is some coffeescript I've been using for this:

````coffeescript
promised = (initialValue, func) ->
  flightKey = '_promisedInFlight' + Ember.guidFor({})
  (k,v) ->
    return v if arguments.length > 1
    this[flightKey] ?= 0
    this[flightKey] += 1
    myNumber = this[flightKey]
    func.apply(this, [k,v]).then((result) =>
      if this[flightKey] <= myNumber
        @set(k, result)
      this[flightKey] -= 1
    )
    initialValue
```

You can use it like this:

```
myThing: promised('loading...', function(k,v){ return somePromise() })
```

This `initialValue` lets you pick what the value of the computed property should be while the promise is still unresolved.
11 Likes

Speaking generally about the idea:

I’m not a fan of making use of promises at the property level. It’s only a minor convenience and throws out a lot of what makes promises useful, i.e. sane async error handling. If you set a property to a promise and want that to show up in a template, via a when helper or whatever, and then that promise rejects… then what? You’re not operating at the proper level to initiate application logic to handle that error case. It makes sense for that promise logic to be initiated at a higher level, which, when the promise resolves, can set a static property, and if things go wrong, it can display an error message, transition elsewhere, etc.

fwiw Angular had this feature for a while, the ability to set a property of $scope to a promise and have that property eventually become the resolved value and have the template update when it resolved, but it was eventually removed since no one was using it and for some of the reasons above.

2 Likes

I’m not a fan of making use of promises at the property level.

Correct me if I’m wrong, but doesn’t Ember Data and Ember Model use for promises for properties? For example:

App.Post = Ember.Model.extend({
    author: Ember.belongsTo('App.Author', {key: "authorId"})
})

App.Author = Ember.Model.extend({
    firstName: Ember.attr()
})

If I have post.author.firstName in my template, isn’t author a promise?

Those are objects that mix in PromiseProxyMixin. It is well documented at PromiseProxyMixin - 4.6 - Ember API Documentation.

@mmun Thanks for the link. I don’t quite understand how the mixin relates to the belongsTo above. Could you explain?

DS.belongsTo returns a computed property which itself returns a PromiseObject. On this page it says “USES: EMBER.PROMISEPROXYMIXIN” which means that it mixes in PromiseProxyMixin.

I actually went with Ember.PromiseObjectMixin on scenarios like this one and it worked great. Feels idiomatic and straight-forward, also encourages separation of concerns.

1 Like

Could you give an example of using the promise proxy? Thanks!

1 Like

Bump!

Ember data is making heavy use of promises and I have currently no idea how I should deal with async one-to-one relations via the Ember way™. [Dealing with async one-to-many relations does work pretty well because of @each observing.]

Consider this:

currentUser: Ember.computed.alias('controllers.application.currentUser')
isCurrentUser: (->
  @get('currentUser.id') is @get('model.user.id')
).property('model.user')

model.user is an async ember data property and isCurrentUser is currently always false. I see that the approach proposed by @ef4 would work well, but I think this should be doable without hackery, no?

I’ve also been exploring this idea.

I would like to remind everyone of the asynchronous nature of promises as this is not a simple problem.

When you set the value of an unresolved promise to a new promise, the eventually resolved value may never from the previous promise. If the new promise resolves faster, and the old promise eventually resolves, you end up in a wrong state. So you need some way to unregister the callback from the old promise when a new one is being set.

This unregistering can be in a template helper or the computed property itself, but it does needs to exist somewhere.

Anyone ? I am also interested to see common patterns on that. I have defined everything as {async: true} in my models which causes me a lot of troubles with computed properties in the Ember.

2 Likes