AngularJS : Should service's boolean method return promise that resolves to true/false, or that gets resolved/rejected?
Asked Answered
P

1

7

The patterns of promises usage still confuse me.

For example, in Angular application, I have a service usersService with method emailExists(email). Obviously, it performs request to the server to check whether given email already exists.

It feels natural for me to make the method emailExists(email) to return promise that in normal operation resolves to either true or false. If only we have some unexpected error (say, server returned 500: internal server error, then promise should be rejected, but in normal operation, it is resolved to corresponding boolean value.

Hovewer, when I started implementing my async validator directive (by $asyncValidators), I see that it wants resolved/rejected promise. So, by now, I ended up with this rather ugly code:

'use strict';

(function(){

   angular.module('users')
   .directive('emailExistsValidator', emailExistsValidator);


   emailExistsValidator.$inject = [ '$q', 'usersService' ];
   function emailExistsValidator($q, usersService){
      return {
         require: 'ngModel',
         link : function(scope, element, attrs, ngModel) {

            ngModel.$asyncValidators.emailExists = function(modelValue, viewValue){
               return usersService.emailExists(viewValue)
               .then(
                  function(email_exists) {
                     // instead of just returning !email_exists,
                     // we have to perform conversion from true/false
                     // to resolved/rejected promise
                     if (!email_exists){
                        //-- email does not exist, so, return resolved promise
                        return $q.when();
                     } else {
                        //-- email already exists, so, return rejected promise
                        return $q.reject();
                     }
                  }
               );
            };
         }
      }
   };

})();

It makes me think that I should modify my service so that it returns resolved/rejected promise instead. But, it feels a kind of unnatural for me: in my opinion, rejected promise means "we can't get result", not "negative result".

Or, do I misunderstand the promise usage?

Or, should I provide two methods? What is the common pattern to name them?

Any help is appreciated.

Patricio answered 29/7, 2015 at 14:15 Comment(4)
The design of $asyncValidators seems to be messed up when it expects exceptions.Garlan
See also When to reject/resolve a promise and Why are exceptions used for rejecting promises in JS?. Rejections should be exceptional.Garlan
Promise rejections are exceptions, the golden rule is to use promises where-ever you'd use exceptions in synchronous code. Would you use exceptions if the code was completely synchronous and IO was instant? If you would use rejections, otherwise use values.Corie
@BenjaminGruenbaum, thanks. I'd love to, but Angular forces me to do, at least, this conversion. This Angular design looks strangely indeed.Patricio
H
2

In this case there is no correct/incorrect approach to this problem. What you are saying about your email check service sounds resonable: in fact, existence of the email in database is not strictly denotes a failure scenario, promise rejection usually corresponds to and reflects.

On the other hand, the way Angular implemented their async validators also makes sense, if you think about it. Failed validation result conceptually feels like a failure, not in terms of HTTP, but in sense of business logic.

So in this case I would probably go and adjust my custom service to at least return non-success status, like 409 Conflict.

If you still want to return 200 success code along with true/false resonses you can still make validator code a little bit less ugly:

ngModel.$asyncValidators.emailExists = function (modelValue, viewValue) {
    return usersService.emailExists(viewValue).then(function (email_exists) {
        return $q[email_exists ? 'when' : 'reject']();
    });
};
Hoi answered 29/7, 2015 at 14:38 Comment(4)
Thanks. Although I was thinking of return email_exists ? $q.reject() : $q.when(); , I like your variant $q[email_exists ? 'when' : 'reject'](); more.Patricio
However, "failed validation" ("invalid input") is not the same as "failure to validate" ("no validation available"). Angular seems to miss that distinction.Garlan
I agree, however Angular chose to go this way which allowed them to have "elegant" code for async validation (check here). I imagine, that otherwise they would have to handle it something like this. I guess instructing that service must return truthy/falsy values for async validators didn't feel cool enough, so they went with resolved/rejected promises to indicate validation result.Hoi
@dfsq: I guess the bigger concern was what to do when the validation threw an exception. It would have required showing an error message or so, instead of just setting the validity to false.Garlan

© 2022 - 2024 — McMap. All rights reserved.