javascriptember.jshtmlbars

Resolving promise doesn't show data in template


I have tried several versions/attempts to create and return a RSVP.Promise as a param to my template.

All the console.log give reasonable values, so the promises are resolving. The problem I have (which is then also the question) is how to return that resolving values to my template.

Here are the versions I've tried:

// in controller.js
testA: Ember.computed('sessionAccount.account.id', function() {
    let _this = this;
    let promise = new Ember.RSVP.Promise(function(resolve, reject) {
        _this.get('store').findAll('accounts2workgroup').then(function(a2ws) {
            let workgroups = [];
            a2ws.forEach(function(a2w){
                if(a2w.get('rights')>1) {
                    workgroups.push(a2w.get('workgroup'));
                }
            });
            console.log(workgroups);
            _this.set('wgAsAdmin', workgroups); // this works
            resolve(Ember.A(workgroups));  //=> [Object] in rendered template
            // return workgroups; // no, not that way
        });
    });

    promise.then(function(data) {
        console.log('did resolve');
        console.log(data);
    })

    return promise; 
}).property('sessionAccount.account.id'),

testB: Ember.computed('sessionAccount.account.id', function() {
    return new Ember.RSVP.Promise(function(resolve, reject) {
    let workgroups = Ember.ArrayProxy.create([{'label': 'TestB Label'}]);
        resolve(workgroups);

    });
}),

testC: Ember.computed(function() {
    return this.store.findAll('artists2workgroup').then(function(a2ws) {
            let workgroups = [];
            a2ws.forEach(function(a2w){
                if(a2w.get('rights')>1) {
                    workgroups.push(a2w.get('workgroup'));
                }
            });
            console.log(workgroups);
            return workgroups; //=> [Object] in rendered
    });
}),

testD: Ember.computed(function() {
    return this.store.findAll('workgroup'); // this of course works, but that's not what I want...
}),

in my template I test all my tests like so:

<h4>TestB</h4>
{{#each testB as |wg|}}
        {{wg}}<br>
        {{wg.label}}<br>
{{/each}}
testB: {{testB}}<br>
testB.length: {{testB.length}}<br>

and all (but the last testD obviously) render to

TestB
testB: [object Object]
testB.length:

though I would expect/want them to show

TestB
<DS.PromiseObject:ember1117>
BB-Promotion
testB: <DS.PromiseObject:ember1117>
testB.length: 1

I know there are ways around that (I can set another property when resolving f.e.), but want to do it the right way and learn how to do this. And I know, that these examples don't make too much sense. That's just the basic functionality, it will be enhanced once I get this running.


Solution

  • First please avoid the explicit promise construction antipattern! Also you don't have to save this, because you have arrow functions in ember-cli. So lets rewrite your testA:

    testA: Ember.computed('sessionAccount.account.id', function() {
        return this.get('store').findAll('accounts2workgroup').then(a2ws => {
            return workgroups
                .filter(a2w => a2w.get('rights') > 1)
                .map(a2w => a2w.get('workgroup'))
        });
    }).property('sessionAccount.account.id'),
    

    Now this won't make it work. The problem here is that ember templates are not promise-aware. So you have three options:

    1. Just don't do this. Often you can use the routes model hook to do async work.
    2. Use something like ember-promise-helpers from the template.
    3. Don't just return a Promise.

    If you can't do 1, I recommend to go with 3. For this you need to understand the PromiseProxyMixin. In ember-data you have two implementations for this Mixin, the PromiseArray and PromiseObject

    All ember-data methods like findAll, findRecord, query or async relationships return a PromiseObject/PromiseArray. So they are both, promise and regular object. The promise part is useful in the routes model hook, and the Object/Array part is useful for computed properties. So the easiest way to go for you, is to split your CP into two:

    allWorkgroups: Ember.computed(function() {
        return this.get('store').findAll('accounts2workgroup');
    }),
    testA: Ember.computed('sessionAccount.account.id', 'allWorkgroups.@each.rights', 'allWorkgroups.@each.workgroup', function() {
        return this.get('allWorkgroups')
            .filter(a2w => a2w.get('rights') > 1)
            .map(a2w => a2w.get('workgroup'))
    }).property('sessionAccount.account.id'),
    

    This will work, because first allWorkgroups will be an empty array/unresolved promise, but then when the promise resolves the array gets updated, and the testA CP will recompute.

    However you can also manually create a new PromiseArray:

    testA: Ember.computed('sessionAccount.account.id', 'allWorkgroups.@each.rights', 'allWorkgroups.@each.workgroup', function() {
        const promise = this.get('store').findAll('accounts2workgroup').then(a2ws => {
            return workgroups
                .filter(a2w => a2w.get('rights') > 1)
                .map(a2w => a2w.get('workgroup'))
        });
    
        return DS.PromiseArray.create({promise});
    }).property('sessionAccount.account.id'),
    

    However you should know, that in both cases you won't get any information if the Promise fails!