0

In building out a service layer within an Angular app I noticed a lot of code repetition to create promises therefore tried making a generic getPromise service as 90% of the promises are the same structure.

Although, when returning the promise from the service the function doesn't execute as normal - even though the returned object is the same.

Has anyone tried doing this? E.g.

angular.module('fooApp')
.service('promise', function promise($q, $http) {
    return {
        getPromise: function (url, data) {

            var deferred = $q.defer();

            $http.post(url, data)
            .success(function (data) {
                deferred.resolve(data);
            })
            .error(function (error) {
                deferred.reject(error);
            });

            return deferred.promise;
        }
    };
})

.service('foo', function foo(config, promise) {
    return {
        getFoo: function (userId) {
            var deferred = $q.defer();
            var url = config.svcUser + 'GetFoo';
            var data {
                userId: userId
            };

            $http.post(url, data)
            .success(function (data) {
                deferred.resolve(data);
            })
            .error(function (error) {
                deferred.reject(error);
            });

            return deferred.promise;
        },
        getFoo2: function (userId) {
            var url = config.svcUser + 'GetFoo';
            var data {
                userId: userId
            };
            return promise.getPromise(url, data);
        }
   }
})


.controller('AgenciesCtrl', function ($scope, agency) {

    agency.getFoo().then(function (agencies) {
        // does fire
    });

    agency.getFoo2().then(function (foo) {
        // does not fire
    });

    $scope.loadAgency = function (agencyId) {
        agency.getFullAgencyProfile(agencyId).then(function (agency) {
            $scope.agency = agency;
        });
    }

});

The issue seems fairly light in this small example but I'm planning on implementing 30+ services so it will reduce code repetition a lot if possible.

  • 1
    I see this all the time, like @MaxFichtelmann said `$http.post` returns a promise, no need to wrap it with `$q` I'm not sure why people don't understand this. Also nice idea with the service to wrap code X with a promise but you can already do this with `$q.when(...)` = *Wraps an object that might be a value or a (3rd party) then-able promise into a $q promise* – m.e.conroy Nov 11 '14 at 21:39
  • I think (and that is the reason I removed my original comment) that the reason is to flatten the response and discard information about status and headers. But giving that another thought seems like a bad idea for itself, because discarding the status for errors may quickly become a huge problem. – Max Fichtelmann Nov 11 '14 at 21:46

2 Answers2

0

You are right! Your code does repeat itself - this is called the deferred anti-pattern.

Promises abstract values that are temporal - that is: they're not available yet. Just like your code normally chains (one line after the next) so do promises with then.

Angular promises, as A+ promises offer a pretty modern implementation and everything Angular does asynchronously and is logically one time operation already returns a promise. This includes everything $http does, as well as $timeout, $resource and so on.

Your code could be much simpler:

angular.module('fooApp')    
.service('foo', function foo(config, promise) {
    return {
        getFoo: function (userId) {
            var url = config.svcUser + 'GetFoo';
            var data { userId: userId };
            return $http.post(url, data).then(function(resp){
                 // any validations you need on the response go here
                 return response.data;
            });
        }
   }
}).controller('AgenciesCtrl', function ($scope, foo) {
    foo.getFoo().then(function (agencies) {
        // does fire, and schedules a digest because it's an Angular promise
        $scope.someVar = agencies; // safe to do here, since a digest will happen
    }); 
});
Community
  • 1
  • 1
Benjamin Gruenbaum
  • 270,886
  • 87
  • 504
  • 504
  • Thanks Ben, I actually managed to get to this point after looking into the promise returned from Angular's $http function in more detail. When you mention // any validations... are you suggesting this could manage either an error/success response based on the returned object? That was the issue I was then up against. I know, ideally the response would always be successful but that's not always the case for the API I'm working with. I have to do some validation on the response to manage the returned state (hence why the Q. promise seemed a good solution before). – JamesEddyEdwards Nov 23 '14 at 22:51
  • Any additional info would be great and thank you for what you've clarified above, appreciate it! – JamesEddyEdwards Nov 23 '14 at 22:54
  • Sure - that's exactly what I meant with `//any validation`. if you think my answer solved your issue consider accepting it (don't feel pressured though). – Benjamin Gruenbaum Nov 23 '14 at 22:59
  • So based on the above how would you handle and return an error (or catch) if the response returned an empty string? (for example) – JamesEddyEdwards Nov 24 '14 at 09:11
-1

Here's what I normally do

In service method call:

return $http.post(url,data,{})
    .success(function(response){
        return response.data;
    })
    .error(function(response){
        // return error / status code
    }); // end $http.post

Then in controller:

var data = someSrvc.methodCall(); 

// you could also follow this up with
$q.when(data).then(...);

Or

In service:

return $http.post(url,data,{});

Then in controller (or where ever):

someSrvc.methodCall().then(function(response){
    // some success code
},function(response){
    // some error code
}); // end methodCall

$q.when make something into a promise example:

$q.when((someVal <= 0) && (someOtherVal > 0))
    .then(function(){
        // do something when condition is true
    });

I sometimes do this when I'm reusing an array but need it to be emptied, the fastest way to truncate an array is to pop off all its values NOT set its length directly to zero or set it to an empty array. I know that seems counter intuitive but look it up see for yourself. So any way something like this:

while(myArray1.length > 0)
    myArray1.pop();

while(myArray2.length > 0)
    myArray2.pop();

$q.when((myArray1.length <= 0) && (myArray2.length <= 0)).then(function(){
    // do something
});
m.e.conroy
  • 3,508
  • 25
  • 27
  • Thank you, this is great. I wasn't aware that $http returns a promise. I'm fairly new to Angular so info like this is really useful. – JamesEddyEdwards Nov 12 '14 at 09:06
  • 2
    This is full of bad advice, `success` does not chain (it returns the same promise) - your usage of `$q.when` is pointless, risky and deferrs for no reason (no point in doing async when it is not required, it's just slow and confusing), you're also doing `$q.when` for no good reason on things that are already promises. – Benjamin Gruenbaum Nov 22 '14 at 16:53