7

I've a bunch of functions which are nested due to top level function is a ajax request. So i want to return a value instead of a promise in nested child function.

Parent

let getUserPermissions = function(id) {
      let deferred = $q.defer();
      let promise = accessRequestService.getPermissions(id);
      promise.then(function(data) {
        deferred.resolve(data);
      }, function(err) {
        deferred.reject(err);
      })
      return deferred.promise;
    }

Child 1

$rootScope.userInit = function() {
        return getUserPermissions(vzid)
          .then(function(data) {

            //Some code here

            return data;
          })

    }

Child 2

let checkAuthorize = function(toState) {
  return $rootScope.userInit().then(
    function(data) {
//some code here 
      return data;
    });
}

Level 3

checkAuthorize(toState).then( function(val){ 
 $rootScope.isAuthorized = val;
  if ($rootScope.isAuthorized == true) {
        $log.info('is Authorized')
      } else {
        $log.info('is not Authorized');
        throw new AuthorizationError()
      }
  })

At Level 3 we are still working with a promise. Can child 2 return a value instead of promise.

Expectation @ Level 3

$rootScope.isAuthorized = checkAuthorize(toState);

  if ($rootScope.isAuthorized == true) {
      $log.info('is Authorized')
      } else {
      $log.info('is not Authorized');
      throw new AuthorizationError()
     }
Sumit Ridhal
  • 1,359
  • 3
  • 14
  • 30
  • 3
    Isn't `getUserPermissions` equivalent to simply `function(id) { return accessRequestService.getPermissions(id); }`? No need to mess around with `Deferred`. – Thomas Jun 21 '17 at 13:12
  • @Thomas that's for another computation based on success/error sent by service. Not present in this code. – Sumit Ridhal Jun 21 '17 at 13:28
  • 1
    Then you can still do `function(id) { return accessRequestsService.getPermissions(id).then(function(data) { ... }, function(err) { ... }); }`. You rarely need to use `defer()` directly unless you're writing your own promise "source". – Thomas Jun 22 '17 at 07:59
  • You **can't** return a value instead of a promise, that's the point in asynchronous execution, you handle the value **latter** when it's available in a callback or promise – Omri Luzon Jul 01 '17 at 18:50
  • Possible Dup: [Returning a value from a Promise](https://stackoverflow.com/questions/25530263/returning-a-value-from-a-promise) – Omri Luzon Jul 01 '17 at 18:55
  • 1
    `async-await` is what you need, but depends on ES version you are targetting – harishr Jul 03 '17 at 14:37
  • What does `es6-promise` tag do there? There are no ES6 promises in the code above, and the code above won't work as expected if $q promises would be replaced with ES6 promises. – Estus Flask Jul 06 '17 at 13:02

6 Answers6

6

The hard truth is: you can't, unless you want spaghetti code all around.

The best solution would be to use something like ui-router's resolve, getting all the permissions needed before the page is shown to the user. Then, you could use them on your controllers without any asynchronous calls.

tiagodws
  • 1,345
  • 13
  • 20
  • I think you can't even WITH spaghetti code all around – Icycool Jul 03 '17 at 12:29
  • Don't underestimate the power of spaghetti code haha @lcycool – tiagodws Jul 03 '17 at 12:33
  • @tiagodws Great suggestion but we are still using a very different UI Routing Methods. Appreciate very much. – Sumit Ridhal Jul 06 '17 at 13:00
  • I'm using `$state.transitionTo` Method to be called before `stateChangeStart` . `$state.transitionTo = function(to, toParams, options) { return checkAuthorize(to).then(function(auth) { $rootScope.isAuthorized = auth; return transitionTo(to, toParams, options) }) }` – Sumit Ridhal Jul 06 '17 at 13:05
3

You can use for it async/await construction. And use Babel for support old browsers.

function resolveAfter2Seconds(x) {
  return new Promise(resolve => {
    setTimeout(() => {
      resolve(x);
    }, 2000);
  });
}

async function f1() {
  var x = await resolveAfter2Seconds(10);
  console.log(x); // 10
  console.log('done');
}

f1();
Vladimir Korshunov
  • 3,090
  • 3
  • 20
  • 25
  • 1
    Native promises (and thus async/await) isn't a solution that should be suggested for AngularJS app unless required, due to the fact that it doesn't take Angular digest cycles into account. async/await won't work as expected in Angular app, while $q promises will. – Estus Flask Jul 06 '17 at 13:03
  • @estus $q is a Promises/A+-compliant implementation of promises/deferred objects inspired by Kris Kowal's Q. Async/Await syntax support for Q promises — https://github.com/audreyt/q-jscex – Vladimir Korshunov Jul 06 '17 at 21:23
  • I'm aware of this. I assume you're not AngularJS user. There are fundamental differences in $q implementation that distinguish it from other Promise/A+ implementations. $q is tightly tied to digest cycle and can be synchronous, other implementations aren't. If Angular user will throw async/await to the app without additional measures this may lead to unforseen results. The answer in its current state cannot be be beneficial for AngularJS users because it doesn't address the issues that are specific to the framework. – Estus Flask Jul 06 '17 at 21:44
0

Yes, this type of thing is possible, but it will change the behavior. You'll probably want to keep userInit, but you also add a userInitValue variable and initialize it as follows:

let userInitValue = null;
let userInit = function() {
    return getUserPermissions()
      .then(function(data) {
        userInitValue = data;
        return data;
      })
}

So now userInitValue will start as null and then later be initialized to the relevant data.

function isKnownAuthorized(toDoSomething) {
    // If we don't know whether the user is authorized
    //   because we are still waiting for the server to tell us
    //   then return false and disallow access for now
    if(!userInitValue) return false;

    // Otherwise return the truth
    //   (as of when we got the server response)
    return userInitValue.isAuthorized(toDoSomething);
}

Note again the change in behavior. The price of getting an instant response, perhaps before the server gives you the data, is that the instant response could be wrong. So don't use this in a one-time :: expression in AngularJs.

Martin Randall
  • 308
  • 2
  • 9
0

Based on what you're hoping to achieve in Level 3, I'm guessing this function is going to be called multiple times with the same input. In this case, what I would do is make the call to the promise if there is not a cached result, and cache the result. This way you don't have to go down the promise chain, although I only count one promise in the code provided. There are multiple handlers on resolve, but only one promise.

Dacheng
  • 156
  • 4
0

You can run your code as if it was synchronous using nsynjs: it will evaluate code step-by-step, and if some function returns promise, it will pause execution, wait until promise is resolved, and assigns resolve result to data property. So, code below will be paused on level 1 until promise is resolved to actual value.

var getUserPermissions = function(id) {
    return new Promise(function(resolve, reject) {
        setTimeout(function(){
        resolve({
                id: id,
                isAdmin: "yes he is",
            })
        }, 1000);
    });
};

function synchronousCode() {
    console.log("start");
    var vzid = 35;
    var userInit = function() {
        return getUserPermissions(vzid).data;
    };
    var checkAuthorize = function() {
     return userInit().isAdmin;
    };
    var isAuthorized = checkAuthorize();
    console.log(isAuthorized);
};
 
nsynjs.run(synchronousCode, null, function(){
 console.log("finish");
});
<script src="https://rawgit.com/amaksr/nsynjs/master/nsynjs.js"></script>
amaksr
  • 7,555
  • 2
  • 16
  • 17
0

I'm using $state.transitionTo Method to be called before $stateChangeStart.

var transitionTo = $state.transitionTo;
$state.transitionTo = function(to, toParams, options) {
  var from = $state.$current,
    fromParams = $state.params;

  to = to.name ? to : $state.get(to);

    $rootScope.state = {
      to: to.self,
      toParams: toParams,
      from: from.self,
      fromParams: fromParams,
      options: options
    }

  if (options.notify && options.notify !== false) {
    return $q.reject(new AuthorizationError('Rejecting $state.transitionTo', 'Transition Rejected'));
  } else {
    return checkAuthorize(to).then(function(auth) {
      $rootScope.isAuthorized = auth;
        return transitionTo(to, toParams, options)
    })
  }
}

StateChangeStart

$rootScope.$on('$stateChangeStart', function(event, toState, toParams, fromState, fromParams) {
  $log.info("Route change start from", fromState.url, "to", toState.url);
  //event.preventDefault();

  if ($rootScope.isAuthorized == true) {
    $log.info('is Authorized')
    //$state.go($rootScope.toState.name);
  } else {
    event.preventDefault();
    $log.info('is not Authorized');
    throw new AuthorizationError('User is not Authorized.', 'NOT_AUTHENTICATED')
  }

});
Sumit Ridhal
  • 1,359
  • 3
  • 14
  • 30