Promises and computed properties

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

I have a very similar situation where I have a route that displays a filtered array based on a query param.

App.Location = DS.Model.extend({
   ...
   sites: DS.hasMany('site', { async: true })
   ...
});

App.Site = DS.Model.extend({
   ...
   siteCategory: DS.belongsTo('site-category', { async: true })
   ...
});

App.SiteCategory = DS.Model.extend({
   ...
   name: DS.attr('string')
   ...
});

My route returns back all related sites for a location then each tab sets the query param to a different siteCategory name to filter the data. This works fine when clicking from one tab to another but not on the initial load due to unresolved promises:

queryParams: ['siteCategory'],
siteCategory: null,

sortProperties: [ 'isActive', 'name' ],
sortedContent: Ember.computed.sort('model', 'sortProperties'),
filteredContent: Ember.computed('siteCategory', 'model.@each.isDeletedFromApplication', function () {
    var self = this;
    var sites = this.get('sortedContent');
    var siteCategory = this.get('siteCategory');

    return sites.filter(function (site) {
        // logs UNDEFINED on page load
        // logs the proper value when the queryParam changes
        console.log(site.get('siteCategory'),get('name');
        return site.get('siteCategory').get('name') === siteCategory && 
               !site.get('isDeletedFromApplication');
    });
})

Would love to know how to work around the unresolved promises in filteredContent on first load.

The approach described by ef2 above should work here. I used it like so: https://gist.github.com/manmal/4a9836922e11157d7ecf (actual working example).

I haven’t written Ember code for a while now, but what I think could work in your example is to create a big, fat promise for all that stuff that you currently have inside the function() {...}, and then use it like so (using promisedProperty.coffee from my gist):

filteredContent: promisedProperty(someDefaultValue, -> 
  # insertYourBigFatPromiseDefHere
).property('sortedContent', 'siteCategory')
1 Like

I’ve been toying with this issue lately as well…Particularly the case where I want to show feedback of the promise’s state (loading/error). I hacked together this {{render-with-promise}} helper as a proof of concept of what IMO is gold. Basically just removes the state checking boilerplate and gives you loading/error psuedo-substates for free in the same fashion as route-backed promises do, but unlike routes you can render multiple promise-based resources on the same page at once since you don’t need to maintain URL state.

Most often, this is handy for dashboard/widget style views where you are loading many different calls individually and don’t want to halt the page transition (as RSVP.hash() would).

Feedback welcome.

2 Likes

Are these issues between promises and computed properties are gonna be fixed? (next release of Ember data) I would like to use Ember data in one of my projects but I’m concern about this… It’s actually pretty hard to find a work around. There’s few month I read that the DataBoundPromise were supposed to be used inside computed properties. What about it ?

Would love to know if someone has some work around or feedback.

Would love to see some examples for this as well. I have a firebase backed project full of async relationships.

What I have been doing is adding a _resolve property to my models, which build new properties based on relevant promise-returning properties. (Convention – for a promise property _foo I have a property foo which is undefined when promise unresolved and then set to the resolution when it resolves.)

I get _resolve in root afterModel … this works, but involves some unfortunate coupling & boilerplate. One problem is that I need to call _resolve on all the models used – so root needs to know what is used (See Mapping queryParams -- proposal or request for best practice).

Currently, using the router seems to be the “best practice” for resolving promises… but the mapping from routes to application state isn’t perfect.

Isn’t this a replication of the PromiseObjectMixin ?

1 Like

I could have been using PromiseObjectMixin (wasn’t aware of it). Either way the coupling problem remains as routes can’t be specified in enough precision to capture application state, and they need to know what to resolve whatever the method.

This does the right thing and I think will help me work around some hacky code in our app but Ember is complaining that I can’t call set because this has already been destroyed:

Assertion failed: calling set on destroyed object

The promise is a Q promise not Ember.RSVP.Promise if that makes a difference. Any ideas how to work around this?

Thanks!

That error means that your object got destroyed while the promise was still resolving. You can defend against that by checking this.isDestroyed before calling set.

1 Like