r3plica
r3plica

Reputation: 13397

angularjs returning rejected promises

I have some strange behaviour here.... I have a service I created to handle all API calls. It looks like this:

angular.module('widget.data').service('apiHandler', apiHandler);

apiHandler.$inject = ['$http', 'SimpleCache', 'apiUrl', 'ngNotify'];

function apiHandler($http, simpleCache, apiUrl, ngNotify) {

    return {
        url: apiUrl,

        get: get,
        post: post,
        put: put,
        delete: requestDelete
    };

    //////////////////////////////////////////////////

    // GET
    function get(url, params) {
        return buildRequest(url, 'GET', null, params);
    };

    // POST
    function post(url, data) {
        return buildRequest(url, 'POST', data);
    };

    // PUT
    function put(url, data) {
        return buildRequest(url, 'PUT', data);
    };

    // DELETE
    function requestDelete(url, params) {
        return buildRequest(url, 'DELETE', null, params);
    };

    // Private function to build our request
    function buildRequest(url, method, data, params) {

        //console.log(url);

        // Create our apiPath
        var apiPath = url.indexOf('http') > -1 ? url : apiUrl + url;

        // Create the model
        var model = {
            method: method,
            url: apiPath,
            data: data,
            params: params,
            cache: simpleCache
        };

        // If we are performing an update/create or delete call
        if (method !== 'GET') {

            // Remove from our cache
            simpleCache.remove(apiUrl + url);
        }

        // Build our request
        return $http(model).then(function (response) {

            // Return our response
            return response.data;

            // If we have an error
        }, function (response) {

            console.log(response.data.exceptionMessage);

            // Display our error
            ngNotify.set(response.data.exceptionMessage, { type: 'error' });

            // Return our error
            return response;
        });
    };
};

You can see in buildMessage it returns the call, response and error response. So I would expect that if there was an error, any service that has this injected into it would also fire the error callback. But it doesn't. I have this service:

angular.module('widget.directives').service('pkAuthenticateService', pkAuthenticateService);

pkAuthenticateService.$inject = ['apiHandler'];

function pkAuthenticateService(apiHandler) {
    return {
        register: register
    };

    //////////////////////////////////////////////////

    function register(model) {

        return apiHandler.post('users/create', model).then(function (response) {
            console.log('success', response);
            return response;
        }, function (response) {
            console.log('error', response);
            return response;
        });
    };
};

But the message I get in the console is the success message and not the error. Can someone explain to me why? Or help me get it working as I would expect (i.e. if it fails in the parent service, then it should fail all the way down).

Upvotes: 2

Views: 1294

Answers (3)

Michał Sałaciński
Michał Sałaciński

Reputation: 2266

It's one of the most interesting & confusing cases in JS promises - "swallowing" of errors. Consider following case:

var x = new Promise((result,reject)=>{
  reject('something bad')
})

x.then(()=>{
  console.log('wow')
  return 'a';
},()=>{
  console.log('ops')
  return 'a';
}).then(()=>{
  console.log('I\'m baaack')
  return 'a';
},()=>{
  console.log('still bad?')
  return 'a';
})

It might be counterintuitive, but inside 2nd then() 'I\'m baaack' will be printed because you have already caught & handled error. So, if you're handling error, either with 2'nd parameter of then() or with some "catch", you need to throw an error or return something rejected to pass error further down.

In your case, it can be done without $q, just replace 1 line:

        // If we have an error
    }, function (response) {

        console.log(response.data.exceptionMessage);

        // Display our error
        ngNotify.set(response.data.exceptionMessage, { type: 'error' });

        // Return our error
        throw response;
    });

Upvotes: 1

quirimmo
quirimmo

Reputation: 9998

Inject the $q service of angular for managing promises. Then create a deferral from $q and perform your request. Inside the result of your request, resolve or reject the promise providing the data. Then return the deferred object. So changing your service in this way should work:

angular.module('widget.data').service('apiHandler', apiHandler);

apiHandler.$inject = ['$http', 'SimpleCache', 'apiUrl', 'ngNotify', '$q'];

function apiHandler($http, simpleCache, apiUrl, ngNotify, $q) {

    return {
        url: apiUrl,

        get: get,
        post: post,
        put: put,
        delete: requestDelete
    };

    //////////////////////////////////////////////////

    // GET
    function get(url, params) {
        return buildRequest(url, 'GET', null, params);
    };

    // POST
    function post(url, data) {
        return buildRequest(url, 'POST', data);
    };

    // PUT
    function put(url, data) {
        return buildRequest(url, 'PUT', data);
    };

    // DELETE
    function requestDelete(url, params) {
        return buildRequest(url, 'DELETE', null, params);
    };

    // Private function to build our request
    function buildRequest(url, method, data, params) {

        //console.log(url);

        // Create our apiPath
        var apiPath = url.indexOf('http') > -1 ? url : apiUrl + url;

        // Create the model
        var model = {
            method: method,
            url: apiPath,
            data: data,
            params: params,
            cache: simpleCache
        };

        // If we are performing an update/create or delete call
        if (method !== 'GET') {

            // Remove from our cache
            simpleCache.remove(apiUrl + url);
        }

        var deferred = $q.defer();
        $http(model).then(function (response) {

            // Return our response
            $q.resolve(response.data);

            // If we have an error
        }, function (response) {

            console.log(response.data.exceptionMessage);

            // Display our error
            ngNotify.set(response.data.exceptionMessage, { type: 'error' });

            // Return our error
            $q.reject(response);
        });
        return deferred;
    };
};

Upvotes: 0

fredw
fredw

Reputation: 1419

The catch function returns a resolved promise not a rejected promise:

The Promise returned by catch() is rejected if onRejected throws an error or returns a Promise which is itself rejected; otherwise, it is resolved.

In order for the rejected promise to percolate through to the outer function (or to "fail all the way down" as you say), you need to return it as a rejected promise:

    return $http(model).then(function (response) {
       ...
    }, function (response) {
       ...
        // Return our error
        return $q.reject(response);
    });

Upvotes: 1

Related Questions