119

I'm using $http in AngularJs, and I'm not sure on how to use the returned promise and to handle errors.

I have this code:

$http
    .get(url)
    .success(function(data) {
        // Handle data
    })
    .error(function(data, status) {
        // Handle HTTP error
    })
    .finally(function() {
        // Execute logic independent of success/error
    })
    .catch(function(error) {
        // Catch and handle exceptions from success/error/finally functions
    });

Is this a good way to do it, or is there an easier way?

Yosvel Quintero
  • 18,669
  • 5
  • 37
  • 46
Joel
  • 8,502
  • 11
  • 66
  • 115

6 Answers6

106

Promises are an abstraction over statements that allow us to express ourselves synchronously with asynchronous code. They represent a execution of a one time task.

They also provide exception handling, just like normal code, you can return from a promise or you can throw.

What you'd want in synchronous code is:

try{
  try{
      var res = $http.getSync("url");
      res = someProcessingOf(res);
  } catch (e) {
      console.log("Got an error!",e);
      throw e; // rethrow to not marked as handled
  }
  // do more stuff with res
} catch (e){
     // handle errors in processing or in error.
}

The promisified version is very similar:

$http.get("url").
then(someProcessingOf).
catch(function(e){
   console.log("got an error in initial processing",e);
   throw e; // rethrow to not marked as handled, 
            // in $q it's better to `return $q.reject(e)` here
}).then(function(res){
    // do more stuff
}).catch(function(e){
    // handle errors in processing or in error.
});
Benjamin Gruenbaum
  • 270,886
  • 87
  • 504
  • 504
  • 4
    How would you use `success()`, `error()` and `finally()` combined with `catch()`? Or do I have to use `then(successFunction, errorFunction).catch(exceotionHandling).then(cleanUp);` – Joel May 09 '14 at 09:54
  • 3
    @Joel generally, you do not want to ever use `success` and `error` (prefer `.then` and `.catch` instead, you can (and should) omit the `errorFunction` from the `.then` use a c`catch` like in my code above). – Benjamin Gruenbaum May 09 '14 at 11:36
  • @BenjaminGruenbaum could you elaborate why you suggest to avoid `success`/`error`? Also my Eclipse runs amok when it sees the `.catch(`, so I use `["catch"](` for now. How can I tame Eclipse? – Giszmo Mar 29 '15 at 18:43
  • Angular's $http module implementation of the $q library uses .success and .error instead of .then and .catch. However in my tests I could access all properties of the $http promise when using .then and .catch promises. Also see zd333's answer. – Stephan Kristyn Jun 02 '15 at 13:14
  • 3
    @SirBenBenji $q doesn't have `.success` and `.error`, $http returns a $q promise _with the addition_ of the `success` and `error` handlers - however, these handlers do not chain and should generally be avoided if/when possible. In general - if you have questions it is best to ask them as a new question and not as a comment on an old one. – Benjamin Gruenbaum Jun 02 '15 at 13:16
  • This was a direct reply to @Giszmo's question. You orobably missed that ;). – Stephan Kristyn Jun 02 '15 at 15:15
  • @SirBenBenji Oh, sorry you are absolutely right - my bad. – Benjamin Gruenbaum Jun 02 '15 at 15:15
  • Please add this to your answer: "...the success and error handlers - however, these handlers do not chain and should generally be avoided if/when possible. In general...". It would make the answer more thorough and useful, because the OP asks about `.success` and `.error`. I guess [this answer](http://stackoverflow.com/a/33287057/1175496) covers it, but still... – Nate Anderson Dec 16 '16 at 20:24
44

Forget about using success and error method.

Both methods have been deprecated in angular 1.4. Basically, the reason behind the deprecation is that they are not chainable-friendly, so to speak.

With the following example, I'll try to demonstrate what I mean about success and error being not chainable-friendly. Suppose we call an API that returns a user object with an address:

User object:

{name: 'Igor', address: 'San Francisco'}

Call to the API:

$http.get('/user')
    .success(function (user) {
        return user.address;   <---  
    })                            |  // you might expect that 'obj' is equal to the
    .then(function (obj) {   ------  // address of the user, but it is NOT

        console.log(obj); // -> {name: 'Igor', address: 'San Francisco'}
    });
};

What happened?

Because success and error return the original promise, i.e. the one returned by $http.get, the object passed to the callback of the then is the whole user object, that is to say the same input to the preceding success callback.

If we had chained two then, this would have been less confusing:

$http.get('/user')
    .then(function (user) {
        return user.address;  
    })
    .then(function (obj) {  
        console.log(obj); // -> 'San Francisco'
    });
};
Michel
  • 26,600
  • 6
  • 64
  • 69
  • 1
    Also worth noting that `success` and `error` are [only added to the immediate return](https://github.com/angular/angular.js/blob/v1.4.8/src/ng/http.js#L975) of the `$http` call (not the prototype), so if you call another promise method between them (like, you normally call `return $http.get(url)` wrapped in a base library, but later decide to toggle a spinner in the library call with `return $http.get(url).finally(...)`) then you'll no longer have those convenience methods. – drzaus Nov 30 '15 at 19:35
40

I think the previous answers are correct, but here is another example (just a f.y.i, success() and error() are deprecated according to AngularJS Main page:

$http
    .get('http://someendpoint/maybe/returns/JSON')
    .then(function(response) {
        return response.data;
    }).catch(function(e) {
        console.log('Error: ', e);
        throw e;
    }).finally(function() {
        console.log('This finally block');
    });
Yosvel Quintero
  • 18,669
  • 5
  • 37
  • 46
grepit
  • 21,260
  • 6
  • 105
  • 81
11

What type of granularity are you looking for? You can typically get by with:

$http.get(url).then(
  //success function
  function(results) {
    //do something w/results.data
  },
  //error function
  function(err) {
    //handle error
  }
);

I've found that "finally" and "catch" are better off when chaining multiple promises.

justin
  • 651
  • 1
  • 8
  • 18
5

In Angular $http case, the success() and error() function will have response object been unwrapped, so the callback signature would be like $http(...).success(function(data, status, headers, config))

for then(), you probably will deal with the raw response object. such as posted in AngularJS $http API document

$http({
        url: $scope.url,
        method: $scope.method,
        cache: $templateCache
    })
    .success(function(data, status) {
        $scope.status = status;
        $scope.data = data;
    })
    .error(function(data, status) {
        $scope.data = data || 'Request failed';
        $scope.status = status;
    });

The last .catch(...) will not need unless there is new error throw out in previous promise chain.

Yosvel Quintero
  • 18,669
  • 5
  • 37
  • 46
zd333
  • 201
  • 2
  • 3
-3

I do it like Bradley Braithwaite suggests in his blog:

app
    .factory('searchService', ['$q', '$http', function($q, $http) {
        var service = {};

        service.search = function search(query) {
            // We make use of Angular's $q library to create the deferred instance
            var deferred = $q.defer();

            $http
                .get('http://localhost/v1?=q' + query)
                .success(function(data) {
                    // The promise is resolved once the HTTP call is successful.
                    deferred.resolve(data);
                })
                .error(function(reason) {
                    // The promise is rejected if there is an error with the HTTP call.
                    deferred.reject(reason);
                });

            // The promise is returned to the caller
            return deferred.promise;
        };

        return service;
    }])
    .controller('SearchController', ['$scope', 'searchService', function($scope, searchService) {
        // The search service returns a promise API
        searchService
            .search($scope.query)
            .then(function(data) {
                // This is set when the promise is resolved.
                $scope.results = data;
            })
            .catch(function(reason) {
                // This is set in the event of an error.
                $scope.error = 'There has been an error: ' + reason;
            });
    }])

Key Points:

  • The resolve function links to the .then function in our controller i.e. all is well, so we can keep our promise and resolve it.

  • The reject function links to the .catch function in our controller i.e. something went wrong, so we can’t keep our promise and need to reject it.

It is quite stable and safe and if you have other conditions to reject the promise you can always filter your data in the success function and call deferred.reject(anotherReason) with the reason of the rejection.

As Ryan Vice suggested in the comments, this may not be seen as useful unless you fiddle a bit with the response, so to speak.

Because success and error are deprecated since 1.4 maybe it is better to use the regular promise methods then and catch and transform the response within those methods and return the promise of that transformed response.

I am showing the same example with both approaches and a third in-between approach:

success and error approach (success and error return a promise of an HTTP response, so we need the help of $q to return a promise of data):

function search(query) {
  // We make use of Angular's $q library to create the deferred instance
  var deferred = $q.defer();

  $http.get('http://localhost/v1?=q' + query)
  .success(function(data,status) {
    // The promise is resolved once the HTTP call is successful.
    deferred.resolve(data);              
  })

  .error(function(reason,status) {
    // The promise is rejected if there is an error with the HTTP call.
    if(reason.error){
      deferred.reject({text:reason.error, status:status});
    }else{
      //if we don't get any answers the proxy/api will probably be down
      deferred.reject({text:'whatever', status:500});
    }
  });

  // The promise is returned to the caller
  return deferred.promise;
};

then and catch approach (this is a bit more difficult to test, because of the throw):

function search(query) {

  var promise=$http.get('http://localhost/v1?=q' + query)

  .then(function (response) {
    // The promise is resolved once the HTTP call is successful.
    return response.data;
  },function(reason) {
    // The promise is rejected if there is an error with the HTTP call.
    if(reason.statusText){
      throw reason;
    }else{
      //if we don't get any answers the proxy/api will probably be down
      throw {statusText:'Call error', status:500};
    }

  });

  return promise;
}

There is a halfway solution though (this way you can avoid the throw and anyway you'll probably need to use $q to mock the promise behavior in your tests):

function search(query) {
  // We make use of Angular's $q library to create the deferred instance
  var deferred = $q.defer();

  $http.get('http://localhost/v1?=q' + query)

  .then(function (response) {
    // The promise is resolved once the HTTP call is successful.
    deferred.resolve(response.data);
  },function(reason) {
    // The promise is rejected if there is an error with the HTTP call.
    if(reason.statusText){
      deferred.reject(reason);
    }else{
      //if we don't get any answers the proxy/api will probably be down
      deferred.reject({statusText:'Call error', status:500});
    }

  });

  // The promise is returned to the caller
  return deferred.promise;
}

Any kind of comments or corrections are welcome.

Watchmaker
  • 4,908
  • 1
  • 37
  • 38
  • 3
    Why would you use $q to wrap the promise in a promise. Why not just return the promise that is returned by $http.get()? – Ryan Vice Jul 18 '16 at 20:25
  • Because `success()` and `error()` wouldn’t return a new promise as `then()` does. With `$q` we make our factory to return a promise of data instead of a promise of an HTTP response. – Watchmaker Jul 20 '16 at 10:53
  • you response is confusing to me so maybe i'm not explaining myself well. unless you are manipulating the response then you can simply return the promise that $http returns. see this example i just wrote: http://jsbin.com/belagan/edit?html,js,output – Ryan Vice Jul 20 '16 at 21:41
  • @RyanVice I edited my answer to explain better. I hope now my point is more clear. – Watchmaker Jul 21 '16 at 17:42
  • That's a good clarification but checking status for error code is unnecessary as promise will get rejected AFIK – Ryan Vice Jul 21 '16 at 21:22
  • Good point @RyanVice, it is corrected now. Other than that which option would you prefer? – Watchmaker Jul 22 '16 at 17:22
  • You can check my fiddle in the comment above for the pattern I recommend but it's basically ```function search(query) { return $http.get('http://localhost/v1?=q' + query) }``` – Ryan Vice Jul 22 '16 at 21:07
  • Unless you need to do something meaningful with the return there's no value in wrapping a promise in a promise. It'd be like catching and rethrowing an exception without doing anything. There's no value to it. – Ryan Vice Jul 22 '16 at 21:08
  • Agreed @RyanVice but I need to treat the response in the factory in case the api is down (otherwise status is -1). My goal is to encapsulate all the call logic in the factory so that the controller doesn't have to know anything about it. – Watchmaker Jul 23 '16 at 08:36
  • 2
    I don't see the value. It feels unnecessary to me and I reject code reviews on my projects that use this approach but if your getting value out of it then you should use it. I've also seen a few promise in angular best practice articles calling out unnecessary wrapping as a smell. – Ryan Vice Jul 23 '16 at 12:05
  • 2
    This is a [deferred anti-pattern](https://stackoverflow.com/questions/30750207/is-this-a-deferred-antipattern). Read [You're Missing the Point of Promises](https://blog.domenic.me/youre-missing-the-point-of-promises/) – georgeawg May 18 '18 at 15:46