1

I am doing some http calls in Angular and trying to call a different service function if an error occurs. However, regardless of my original service call function return, the promise it returns is always "undefined". Here is some code to give context:

  srvc.sendApplicantsToSR = function (applicant) {
    var applicantURL = {snip};

var promise = $http({
    headers: {
        'Content-Type': 'application/x-www-form-urlencoded'
    },
    method: 'POST',
    url: applicantURL,
    data: applicant
})
  .success(function (data) {
    return data;
  })
  .error(function (error) {
    return [];
  });

  return promise;
  };

Then, in the controller:

for (var applicant in $scope.applicants) {
            $scope.sendATSError($scope.sendApplicantsToSR($scope.applicants[applicant]), applicant);
       }

$scope.sendATSError = function (errorCheck, applicantNumber) {
      if (angular.isUndefined(errorCheck)) {
      console.log(errorCheck);
        AtsintegrationsService.applicantErrorHandling($scope.applicants[applicantNumber].dataset.atsApplicantID);
      }
    };

However, it is always sending errors because every response is undefined. How can I differentiate between the two returns properly? Thank you!

lmcphers
  • 468
  • 3
  • 18
  • Try promise = $http(...); promise.success(...); return promise; – hally9k Nov 12 '15 at 00:02
  • You didn't provide all the relevant code. The first snippet contains srvc.sendApplicantsToSR, and it is $scope.sendApplicantsToSR in the second. And angular.isUndefined will always be false for a promise. @hally9k Promises don't work like that. – Estus Flask Nov 12 '15 at 00:17
  • 1
    So $http doesn't return a promise is that what you're saying? Please explain what you mean by 'Promises don't work like that.' – hally9k Nov 12 '15 at 00:19
  • Woops, sorry @estus, I seemed to paste the wrong srvc code, but the errorHandling one is very similar to the one that I provided. :) Just a different URL and no headers needed for that one. – lmcphers Nov 12 '15 at 00:54
  • Imcphers, it appears that you are not interested in the `data` returned by the HTTP call, only in whether the call was successful or not. Am I right? – Roamer-1888 Nov 12 '15 at 02:38
  • Yes that is correct. However, I had issues placing the code directly in the .success so opted to try it this way. – lmcphers Nov 12 '15 at 02:40
  • OK, in that case some nice convenient syntax is available to you .... I'll write an answer .... – Roamer-1888 Nov 12 '15 at 02:42

3 Answers3

3

Looking at angular documentation, the sample code is

$http({
  method: 'GET',
  url: '/someUrl'
}).then(function successCallback(response) {
    // this callback will be called asynchronously
    // when the response is available
  }, function errorCallback(response) {
    // called asynchronously if an error occurs
    // or server returns response with an error status.
  });

based on that - your first code snippet should be

 srvc.sendApplicantsToSR = function(applicant) {
     var applicantURL = {
         snip
     };

     return $http({
         headers: {
             'Content-Type': 'application/x-www-form-urlencoded'
         },
         method: 'POST',
         url: applicantURL,
         data: applicant
     });
 };
Jaromanda X
  • 53,868
  • 5
  • 73
  • 87
  • I used the documentation to return a promise - not sure what .success/.error are, as that is not documented in angularjs documentation – Jaromanda X Nov 12 '15 at 00:49
  • Okay! I will try this tomorrow and get back to you, but I think this makes sense. Thanks! – lmcphers Nov 12 '15 at 00:53
  • 1
    `success`/`error` were [deprecated](https://docs.angularjs.org/api/ng/service/$http#deprecation-notice). Unlike `then`, they do *not* return a new promise, which breaks chaining, and causes confusion (as demonstrated by this question). – Igor Raush Nov 12 '15 at 01:27
2

You should return your promisse to be handled by the controller itself.

Simplifying:

.factory('myFactory', function() {
    return $http.post(...);
})
.controller('ctrl', function(){
    myFactory()
        .success(function(data){
             // this is your data
        })
})

Working example:

angular.module('myApp',[])
.factory('myName', function($q, $timeout) {
    return function() {
        var deferred = $q.defer();
        $timeout(function() {
            deferred.resolve('Foo');
        }, 2000);
        return deferred.promise;
    }
})
.controller('ctrl', function($scope, myName) {
    $scope.greeting = 'Waiting info.';
    myName().then(function(data) {
        $scope.greeting = 'Hello '+data+'!';
    });
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="myApp" ng-controller="ctrl">
    {{greeting}}!
</div>
Italo Ayres
  • 2,603
  • 2
  • 16
  • 20
2

As others have said, $http's .success() and .error() are deprecated in favour of .then().

But you don't actually need to chain .then() in .sendApplicantsToSR() as you don't need (ever) to process the successfully delivered data or to handle (at that point) the unsuccessful error.

$scope.sendApplicantsToSR = function (applicant) {
    var applicantURL = {snip};
    return $http({
        headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
        method: 'POST',
        url: applicantURL,
        data: applicant
    });
};

Now, in the caller (your line of code in the for loop), a promise is returned (not data) and that promise will, on settling, go down its success path or its error path. Exactly what happens on these paths is determined entirely by the callback functions you write in one or more chained .thens .

So what you need to write is a kind of inside-out version of what's in the question - with $scope.sendApplicantsToSR() on the outside and $scope.sendATSError() on the inside - and linked together with a .then().

for (var prop in $scope.applicants) {
    var applicant = $scope.applicants[prop];
    $scope.sendApplicantsToSR(applicant).then(null, $scope.sendATSError.bind(null, applicant));
}
// Above, `null` means do nothing on success, and 
// `function(e) {...}` causes the error to be handled appropriately. 
// This is the branching you seek!!

And by passing applicant, the error handler, $scope.sendATSError() will simplify to :

$scope.sendATSError = function (applicant) {
    return AtsintegrationsService.applicantErrorHandling(applicant.dataset.atsApplicantID); // `return` is potentially important.
};

The only other thing you might want to know is when all the promises have settled but that's best addressed in another question.

Roamer-1888
  • 19,138
  • 5
  • 33
  • 44
  • Hi there, Roamer - thanks for a very detailed response. I am trying to implement this solution, but I am getting a Compilation error "[Don't make functions within a loop.]" Apparently this error is being thrown by Play because this is a Play application. What do you think I can do about that? – lmcphers Nov 12 '15 at 16:06
  • I fixed it obviously just by moving the function outside of the loop in the controller. So thank you for your help Roamer! :) – lmcphers Nov 12 '15 at 16:40
  • I made a stupid mistake and have edited the code. `applicant` needs to be bound in otherwise the final value of `applicant` will be used in every call of the error handler. – Roamer-1888 Nov 12 '15 at 16:48
  • 1
    Moving the function outside of the loop would fix the compile error but not my "final value" bug. – Roamer-1888 Nov 12 '15 at 16:52
  • Ohh okay! Can you explain why the final value of applicant will be used in every call? – lmcphers Nov 12 '15 at 16:54
  • Also, I added the bind but getting an error that $scope.sendApplicantsToSR is undefined. What is going on there? – lmcphers Nov 12 '15 at 16:57
  • 1
    Due to asynchronicity, the for loop will have comleted before the error handler is ever called. The basic issue is explained [here](http://stackoverflow.com/questions/750486/javascript-closure-inside-loops-simple-practical-example/19323214#19323214). – Roamer-1888 Nov 12 '15 at 17:00
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/94957/discussion-between-lmcphers-and-roamer-1888). – lmcphers Nov 12 '15 at 17:05