Search code examples
ember.jsrsvp.js

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?


Solution

  • 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;
          });
        }
    });