EmberJS fulfill multiple nested promises - ember.js

I want to return an array of objects. My code looks like this:
var apps = [];
this.get('groups').then(function(groups)
{
groups.forEach(function(group)
{
self.store.find('module', group.get('module').get('id')).then(function(module)
{
self.store.find('app', module.get('app').get('id')).then(function(app)
{
if(!apps.contains(app))
apps.pushObject(app);
});
});
});
});
I want to return all apps after foreach-loop is fulfilled, but I have no idea.

You must always return a promise to keep building the chain.
var self = this;
return this.get('groups')
// Return an app for each group
.then(function (groups) {
return Ember.RSVP.Promise.all(groups.map(function (group) {
return self.store.find('module', group.get('module.id'))
.then(function (module) {
return self.store.find('app', module.get('app.id'));
});
}));
})
// Filter out duplicates
.then(function (apps) {
return apps.uniq();
});

Related

Using the result of a promise in Ember.RSVP.hash

I've been pulling out my hair with this for a few hours now so I thought I'd just ask :)
In the model hook of my route, I'm grabbing the account ID from the session store. I'm also returning an Ember hash of layouts using a (presently) hard-coded ID:
model: function() {
var accountId = this.get('session.currentUser').then(function(user) {
return user;
}).then(function(user) {
return user.get('account');
}).then(function(account) {
var accountId = parseInt(account.get('id'));
console.log(accountId); // outputs 2
return accountId;
});
return Ember.RSVP.hash({
layouts: this.store.query('layout', { account_id: 2 })
});
},
/* {{log layouts}} in the template returns the correct list of layouts */
However, when I try and use the value of the first promise in the hash, as follows:
return Ember.RSVP.hash({
layouts: this.store.query('layout', { account_id: accountId })
});
I get the following error:
You must pass a resolver function as the first argument to the promise constructor
TypeError: You must pass a resolver function as the first argument to the promise constructor
I can almost understand this, as perhaps the accountID promise isn't resolved before the hash function is called.
But then I tried:
var _this = this;
var accountId = this.get('session.currentUser').then(function(user) {
return user;
}).then(function(user) {
return user.get('account');
}).then(function(account) {
var accountId = parseInt(account.get('id'));
console.log(accountId); // outputs 2
return accountId;
}).then(function(accountId) {
console.log(accountId); // outputs 2
return Ember.RSVP.hash({
layouts: _this.store.query('layout', { account_id: accountId })
});
});
This does not give any errors, but {{log layouts}} in the template returns 'undefined'.
Can anyone help, please?
Instead of returning the hash at the end, structure your promise the other way around:
var _this = this;
return Ember.RSVP.hash({
layouts: this.get('session.currentUser').then(function(user) {
return user;
}).then(function(user) {
return user.get('account');
}).then(function(account) {
return parseInt(account.get('id'), 10);
}).then(function(accountId) {
return _this.store.query('layout', { account_id: accountId });
})
});

Filtering model data with Ember Enumerable methods

Full code:
http://jsbin.com/xabome/1/edit?html,js,output
I'm attempting to filter some model data by calling .filter on the record array returned from the store, but it has no effect.
My controller:
App.IndexController = Ember.Controller.extend({
fooDataOne: function() {
var fooData = this.get('fooData');
// If uncommented, I return both items
// return fooData;
return fooData.filter(function(item) {
console.log("Why am I not filtering?"); // Never called
if(item.get('id') == 1) { return true; }
});
}.property('fooData')
});
Is it not possible to call .filter as I'm wanting to here? What should I do instead?
The store.find method always returns a Promise.
So you could filter the data in a then block:
setupController: function(controller, model) {
this.store.find('test-data').then(function(data) {
var fooData = data.filter(function(item) {
return item.get('id') == 1;
});
controller.set('fooDataOne', fooData);
});
}
I've found an alternative solution by using an observer and getting a FilteredRecordArray from the store directly:
App.IndexController = Ember.Controller.extend({
fooDataFilter: function() {
var fooData = this.get('fooData');
var fooDataOne = this.store.filter('test-data', function(item) {
if (item.get('id') == 1) { return true; }
});
this.set('fooDataOne', fooDataOne);
}.observes('fooData').on('init')
});
Although the above works, it doesn't explain why the enumerable methods are not working on a a regular DS.RecordArray, which fooData should be.

How do I return the boolean value from this layer-cake of promises?

I've got a controller method that takes a string argument so I can test if a user has a capability. The user has many roles and the roles has an array of permissions attached that we need to check if it contains the capability. I know this is overly verbose, but for the sake of understanding, I've left it so. Will refactor later...
App.WorkspaceIndexController = Ember.Controller.extend({
userCan: function(capability) {
var userHasCapability = false;
var userPromise = this.get('session.user');
var rolesPromise = userPromise.then( function(user) {
return user.get('roles');
});
var filteredRolesPromise = rolesPromise.then(function (roles) {
return roles.filter(function (role) {
return role.get('workspace') === self.get('workspace');
});
});
filteredRolesPromise.then(function (filteredRoles) {
return filteredRoles.forEach(function (role) {
userHasCapability = _.contains(_.flatten(role.get('permissions'), 'name'), capability);
});
});
return userHasCapability;
},
...
});
The problem I'm having, is that I need the method to return a boolean if the user has the permission. This returns false every time. Am I setting the userHasCapability property improperly, or is there something else I should be doing to return the value?
Primitive types such as bool, int, string etc are copied, not referenced. This means you return userHasCapability, and the value false is returned immediately, setting userHasCapability within the scope of that promise, doesn't mean it will be returned. In fact it won't.
Additionally the real response will need to be in the form of a promise, and whomever calls it will need to use it in that form as well.
Here's the order of operations, assuming foo calls userCan.
App.WorkspaceIndexController = Ember.Controller.extend({
foo: function(){
var j = this.userCan('eat worms'); // 1. method is called, 6. j is set to false, we fall out of foo
},
userCan: function(capability) {
var userHasCapability = false;
var userPromise = this.get('session.user');
var rolesPromise = userPromise.then( function(user) { // 2. promise built
return user.get('roles'); // 7. this promise happens is resolved
});
var filteredRolesPromise = rolesPromise.then(function (roles) { // 3. another promise built
return roles.filter(function (role) { //8 return the filter cause 7 resolved
return role.get('workspace') === self.get('workspace');
});
});
filteredRolesPromise.then(function (filteredRoles) { // 4. another promise built
return filteredRoles.forEach(function (role) { //9. 8 resolved so do this now, even though no one references userHasCapability anymore
userHasCapability = _.contains(_.flatten(role.get('permissions'), 'name'), capability);
});
});
return userHasCapability; // 5. false is returned
},
...
});
The fact that roles is a promise means anyone that tries to use it needs to expect a promise as a result (or don't use async, and don't use promises)
App.WorkspaceIndexController = Ember.Controller.extend({
foo: function(){
this.userCan('eat worms').then(function(result){
console.log(result);
});
},
userCan: function(capability) {
var userHasCapability = false;
var userPromise = this.get('session.user');
var rolesPromise = userPromise.then( function(user) { // 2. promise built
return user.get('roles'); // 7. this promise happens is resolved
});
var filteredRolesPromise = rolesPromise.then(function (roles) { // 3. another promise built
return roles.filter(function (role) { //8 return the filter cause 7 resolved
return role.get('workspace') === self.get('workspace');
});
});
return filteredRolesPromise.then(function (filteredRoles) { // 4. another promise built
filteredRoles.forEach(function (role) { //9. 8 resolved so do this now, even though no one references userHasCapability anymore
userHasCapability = _.contains(_.flatten(role.get('permissions'), 'name'), capability);
});
return userHasCapability;
});
}
});

How to chain a RSVP promise and return the original reject/success functions?

I have a simple rsvp helper that lets me wrap an ajax call as a simple promise
var PromiseMixin = Ember.Object.create({
promise: function(url, type, hash) {
return new Ember.RSVP.Promise(function(resolve, reject) {
hash.success = function(json) {
return Ember.run(null, resolve, json);
};
hash.error = function(json) {
if (json && json.then) {
json.then = null;
}
return Ember.run(null, reject, json);
};
$.ajax(hash);
});
}
});
This works great and is then-able like you'd expect. The problem is when I have code that needs another promise that wraps this low level one first.
example
In my ember controller I might do this
Appointment.remove(this.store, appointment).then(function() {
router.transitionTo('appointments');
}, function() {
self.set('formErrors', 'The appointment could not be deleted');
});
In my Appointment model I'm doing this for the "remove"
remove: function(store, appointment) {
return this.xhr('/api/appointments/99/', 'DELETE').then(function() {
store.remove(appointment);
//but how I do return as a promise?
}, function() {
//and how can I return/bubble up the failure from the xhr I just sent over?
});
},
xhr: function(url, type, hash) {
hash = hash || {};
hash.url = url;
hash.type = type;
hash.dataType = "json";
return PromiseMixin.promise(url, type, hash);
}
Current my controller always falls into the "fail" state (even when my ajax method returns a 204 and is successful). How can I do a "chained promise" return from this remove method in my model to enable controllers to invoke it as a "thenable" like I have above?
Couldn't you do something like this?
remove: function(store, appointment) {
var self= this;
return new Ember.RSVP.Promise(function(resolve,reject) {
self.xhr('/api/appointments/99/', 'DELETE').then(function(arg) {
store.remove(appointment);
resolve(arg);
}, function(err) {
reject(err);
});
});
},

How to return a deferred promise and create a model with Ember.Deferred?

I'm trying to create a User.current() in my application, which pulls data from my server using $.getJSON('/users/current', function(data) { ... });. I am using the Singleton method that Discourse uses, which does the following:
Dashboard.Singleton = Ember.Mixin.create({
// See https://github.com/discourse/discourse/blob/master/app/assets/javascripts/discourse/mixins/singleton.js
current: function() {
if (!this._current) {
this._current = this.createCurrent();
}
return this._current;
},
createCurrent: function() {
return this.create({});
}
});
And in my User singleton model, I've rewritten createCurrent as follows:
Dashboard.User.reopenClass(Dashboard.Singleton, {
createCurrent: function() {
return Ember.Deferred.promise(function(p) {
return p.resolve($.getJSON('/users/current').then(function(data) {
return Dashboard.User.create(data);
}));
});
}
});
User is a normal Ember object model:
Dashboard.User = Ember.Object.extend({
});
This does request the data from the server, but the function is not setting User.current() correctly - when I inspect it, User.current() has none of the properties that should be set, such as name.
How can I return and set the current user using Ember's deferred and promises?
That's cause you're returning the promise in place of the user.
Why don't you create the user, then fill in the properties later.
Or use the Promise Proxy pattern that Ember Data uses (the promise can be used as the object once resolved)
DS.PromiseObject = Ember.ObjectProxy.extend(Ember.PromiseProxyMixin);
function promiseObject(promise) {
return DS.PromiseObject.create({ promise: promise });
}
Since $.getJSON('/users/current') returns a promise, might as well use that.
createCurrent: function() {
return $.getJSON('/users/current').then(function(data) {
return Dashboard.User.create(data);
});
}
Then you need to keep in mind that createCurrent returns a promise, not the object itself so you will need to:
current: function() {
if (!this._current) {
var that = this;
this.fetching = true;
this.createCurrent().then(function(val) {
that.fetching = false;
that._current = val;
});
}
return this._current;
},