JPS
JPS

Reputation: 2760

Is it okay to handle all the $http errors in controller?

In all my services, I'm just invoking REST services and returning the promises to the controllers. All the error's are handled at controllers using catch like below,

MyService.getData(url).then(getDataSuccess).catch(exception.catcher('Contact Admin : '));

My question here is, Since the real $http calls will be made at service, should I have to write catchers in service or catching in controller is fine?,

Scenario 1: 
     function getData(url){
             return $http.get(url);
     }

Scenario 2: (Nested calls to make combined results)
     function getOtherData(url){

         var defer = $q.defer();

         $http.get(url).then(
            function(response){
                $http.get(nextService).then(
                    function(res){
                         defer.resolve('combined data');
                    }
                )                      
            }
         );

         return defer.promise;
     }

Both the service method is not handling any errors. Instead it just returns the promise. Will there be any situation where this kind of exception handling will get failed?

Note: I have created decorators for handling javascript,angular errors and route errors separately. This question is particularly about $http service errors.

Upvotes: 1

Views: 91

Answers (3)

georgeawg
georgeawg

Reputation: 48948

Scenario 2: (Nested calls to make combined results)

Failed Scenario

 function getOtherData(url){

     var defer = $q.defer();

     $http.get(url).then(
        function(response){
            $http.get(nextService).then(
                function(res){
                     defer.resolve('combined data');
                }
            )                      
        }
     );

     return defer.promise;
 }

This scenario will fail if the first $http.get has an error. The promise will hang and never get resolved. This is why we recommend avoiding using $q.defer to create promises from services that already return promises.

Instead return data and chain promises.

function getOtherData(url) {
    var promise = $http.get(url);

    var derivedPromise =
        promise.then ( function (response) {
            var data = response.data;
            var nextPromise = $http.get(nextService);
            var derivedNext = nextPromise.then(function(response) {
                 //return for chaining
                 return response.data;
            });
            //return composite for chaining
            return $q.all([data, derivedNext]);
         });

    return derivedPromise;
};

The getOtherData(url) promise will be fulfilled with an array with the data from the two XHRs or it will be rejected with the first error response.

It is possible to create chains of any length and since a promise can be resolved with another promise (which will defer its resolution further), it is possible to pause/defer resolution of the promises at any point in the chain. This makes it possible to implement powerful APIs.1


Chaining error handlers

In an error handler, to convert a rejected resolution to a fulfilled resolution return data. To chain a rejection, throw the error.

For example:

 promise = http.get(someUrl);

 derivedPromise = promise.catch(function(errorResponse) { 
     if (fixable) {
         fixedPromise = $http.get(fixedUrl);
         //return to convert
         return fixedPromise;
     } else {
         //throw to chain rejection
         throw errorResponse;
     }
 };

By chaining error handlers, errors can be handled both by the service and the client of the service.

This makes it possible to implement powerful APIs like $http's response interceptors.1

Upvotes: 1

Duncan
Duncan

Reputation: 95622

Building on @georgeawg's answer, if you want to return multiple sets of data then you don't need nested calls.

function getOtherData(url) {
    var promise1 = $http.get(url).then ( function (response) {
        return response.data;
    });
    var promise2 = $http.get(nextService).then(function(response) {
        return response.data;
    });

    return $q.all([promise1, promise2]);
};

Now the caller gets a promise that resolves to a list of the 2 data items (or is rejected if either request fails). The only real difference is that both requests are issues in parallel.

This generalises easily to a situation where you could have a list of urls, fetch them all in parallel and get an array of the response.data items.

Because you get back only a single promise that resolves to an array of data you can handle the result in the controller, but you only need one error handler.

MyService.getOtherData(url)
.then(getDataSuccess)
.catch(exception.catcher('Contact Admin : '));

Although the original question doesn't specify, it might be the case that the second url depends on the result from the first. You can handle that case here as well if you remember that you can call .then() multiple times on the same promise:

function getOtherData(url) {
    var promise1 = $http.get(url).then ( function (response) {
        return response.data;
    });
    var promise2 = promise1.then(function(response) {
        // compute nextService from response.data here...
        var nextService = foo(response.data); 
        return $http.get(nextService).then(function(response) {
            return response.data;
        });
    });

    return $q.all([promise1, promise2]);
};

Upvotes: 0

charlietfl
charlietfl

Reputation: 171679

Yes what you have can fail triggering your catch because you have no reject().

You are using an anti-pattern creating your own promise and not chaining the nested request properly. Neither of those request rejections will be returned anywhere.

To be able to chain these get rid of the $q.defer() and do:

function getOtherData(url) {

    // return beginning of promise chain
    return $http.get(url).then(function (response) {
        // return next promise
        return $http.get(nextService).then(function (res) {
            // combine and return the data 
            return {
                d1 : response.data,
                d2 : res.data
            };
        });
    });    
} 

Now walk through the scenarios and each part of chain is intact.

Think of the chain as each then needs a return until the end of the chain

Upvotes: 1

Related Questions