angularjs returning rejected promises - javascript

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).

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;
});

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);
});

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;
};
};

Related

Using a service to get data via $http get in AngularJs

I'm trying to pass data back to my controller from my service with no success. I am able to invoke my service from my controller, and I know the service itself works because I can console log the response from within the service (but not back in the controller).
This is my service:
(function () {
angular
.module('app')
.service('testService', testService);
testService.$inject = ['$http'];
function testService($http, url) {
var baseUrl = "http://getjsondata.com/" + url;
this.getData = function (url) {
$http({
method: 'GET',
url: baseUrl + url,
headers: {'Content-Type': 'application/json'}
})
.then(function (response) {
console.log(response); // THIS WORKS
return response;
})
.catch(function (error) {
return error;
});
};
}
}());
This is inside my controller:
vm.getTestData = function (url) {
vm.response = testService.getData(url);
console.log(vm.response);
};
I've tried passing the data back as a callback in testService.getData but with no success. I have also tried using a factory instead of a service but I'm not able to access the data back in my controller. I have also tried defining my function in the service differently (getData: function()...) and so on.
I'm guessing my return statement in the service is behaving differently than the way I am expecting. I would appreciate any help!
getData never returns. add return in front of $http and use .then in your controller.
this.getData = function (url) {
return $http({
method: 'GET',
url: baseUrl + url,
headers: {'Content-Type': 'application/json'}
})
};
In ctrl
vm.getTestData = function (url) {
testService.getData(url).then(function (response) {
vm.response = response;
return response;
})
.catch(function (error) {
return error;
});
};

How can I return a called back function?

I using Angular's $http to get questions, which expects a promise in return, either success or error:
Question.getQuestions().success(function (res) {
...
}).error(function (err) {});
In my method, I need to check if token is expired, if so, refresh, then make the request to /questions and return the promise. Otherwise, just make the request to /questions as usual:
getQuestions: function() {
// this is called by refreshToken but not returning the $http promise
var get = function() {
return $http({
method: 'GET',
url: url + ver + '/questions',
...
});
};
if (Auth.tokenIsExpired()) {
return Auth.refreshToken(get);
} else {
return get();
}
},
The refreshToken is another $http which relies on a promise, then calls the get() callback.
Auth {...
refreshToken: function(callback) {
...
_this.getAuth(OAuth).success(function (access_obj) {
//set token
callback();
})...
Where getAuth is another $http promise:
getAuth: function(params) {
return $http({
method: 'POST',
url: url + '/oauth/access_token',
...
});
},
All of these methods are called as expected, but am getting error:
Cannot read property 'success' of undefined
This is because Auth.refreshToken(get) is not returning the /questions $http call as it should. How can I return the promise from that back to the original Question.getQuestions()?
Here's a suggestion since your code looks over-complicated.
getQuestions: function() {
var get = $http({
method: 'GET',
url: url + ver + '/questions',
...
});
if (Auth.tokenIsExpired()) {
return Auth.refreshToken().then(function(){
return get();
});
} else {
return get();
}
}
refreshToken: function() {
return _this.getAuth(OAuth).then(function (access_obj) {
//set token
});
}
Basically refreshToken should refresh the token and return a promise for when its done. Then when you GET the questions you check if the token is expired, if it did then you call refreshToken and when its done (.success()/.then() is called you do the usual GET)
You can simplify this further and let Auth handle all of the token refreshing for you by moving the line Auth.tokenIsExpired() inside the refreshToken method.
Another example:
getQuestions: function() {
return Auth.refreshToken().then(function(){
return $http.get(url + ver + '/questions');
});
}
refreshToken: function() {
if (_this.tokenIsExpired())
return _this.getAuth(OAuth).then(function (access_obj) {
//set token
});
else return $q.resolve(/*value needed?*/);
}
This is failing because when you refresh the authToken you just call the get but don't return the promise it returns.
Try this:
refreshToken: function(callback) {
...
_this.getAuth(OAuth).success(function (access_obj) {
//set token
return callback();
})...
but I would not mix callbacks and promises, would instead do:
refreshToken: function(callback) {
var promise = $q.defer();
_this.getAuth(OAuth).success(function (access_obj) {
//set token
promise.resolve();
})...
then:
if (Auth.tokenIsExpired()) {
Auth.refreshToken().then(function() { return get() });
} else {
return get();
}
Most preferable to clean this up would be to make the token check return a promise itself that resolves once it confirms or refreshes the token so the final would just be:
Auth.checkToken().then(function() { return get() });

passing error message from service to controller in angularjs

Controller.js
var vm = this;
vm.admin = {};
vm.add = function () {
API.addAdmin(token, vm.admin)
.then(function (resp) {
vm.hideForm = true;
vm.showButton = true;
Notify.green(resp);
}, function (resp) {
Notify.red(resp);
});
};
API.js
function addAdmin(token, dataObj) {
return Constant.getApiUrl()
.then(function (url) {
$http({
method: 'POST',
url: url + '/client/admin',
headers: {
'Token': token
},
data: dataObj
}).then(handleResp);
function handleResp(resp) {
var responseStatus = (resp.status >= 200 && resp.status < 300) ? 'good' : 'bad';
if (responseStatus === 'good') {
console.log("Success" + resp);
return resp;
} else {
console.log("Failed" + resp);
return resp;
}
}
})
}
If I get a success response in API then i need to connect it to success function in my controller and if i get error message in my API, then i need it to connect it to error function in my controller.How should I evaluate the response status from my API(is either success or error).
I don't want to pass successfn, errorfn from my controller to API(only if there's no alternative).
I need to get the response data from API to controller to show it in Notify message.
Thank You!
In service (assign response values in "originalData"):
angular.module('appname').service('myserviceName', function(yourExistingService){
this.myFunction= function(originalData) {
//for next line to work return promise from your addAdmin method.
var promise = yourExistingService.getResponseFromURL(originalData);
return promise;
}
});
And in your controller :
var promise = myserviceName.myFunction($scope.originalData);
promise.$promise.then(function() {
console.log($scope.originalData);
});
And then you can check you "originalData" and write code according to your need.For more detail you can have a look on this http://andyshora.com/promises-angularjs-explained-as-cartoon.html.

Use output from services to controllers

I want to result of my $http.get from my service to my controller.
myserviceSample.js
function messagesService($q,$http){
var messages;
$http({
method: 'GET',
url: 'http://api.com/feedback/list'
})
.then(function success(response){
messages = response.data;
console.log(messages);
},function error(response){
console.log('error'+ response);
});
console.log(messages);
return {
loadAllItems : function() {
return $q.when(messages);
}
};
}
})();
mycontrollerSample.js
function MessagesController(messagesService) {
var vm = this;
vm.messages = [];
messagesService
.loadAllItems()
.then(function(messages) {
console.log(messages);
vm.messages = [].concat(messages);
});
}
})();
The above code results gives undefined output.
What i miss?
$q.when object does expect promise/object to make it working. In your case you have to pass promise object to $q.when as you are doing $http.get call. Here messages object doesn't hold promise of $http.get, so you could change the implementation of method like below.
Service
function messagesService($q,$http){
var messages = $http({
method: 'GET',
url: 'http://api.com/feedback/list'
})
.then(function success(response){
return response.data;
},function error(response){
return $q.reject('Error Occured.');
});
return {
loadAllItems : function() {
return $q.when(messages);
}
};
}
Then controller will resolve that promise & .then will do the trick
function MessagesController(messagesService) {
var vm = this;
vm.messages = [];
messagesService
.loadAllItems()
.then(function(messages) {
console.log(messages);
vm.messages = [].concat(messages);
});
}
Note: Using $q to create a custom promise, is considered as bad pattern when you have $http.get method there(which does return
promise itself)
Improved Implementation
function messagesService($q, $http) {
var messages, getList = function() {
return $http({
method: 'GET',
url: 'http://api.com/feedback/list'
})
.then(function success(response) {
messages = response.data
return response.data;
}, function error(response) {
return $q.reject('Error Occured.');
});
};
return {
loadAllItems: function() {
if (!data)
return getList(); //return promise
else
return $q.resolve(messages); //return data
}
};
};

Then appears to fire before async returns value

I am getting an error almost like my .then is firing before my async call is finished. I realize I am looping for the api post, but i am currently only trying it with an array size of 1.
Service:
this.saveTags = function (tag) {
$http({
method: "POST",
url: '/api/projects/' + data.Id + '/tags',
data: ({ Name: tag.Name })
}).then(function (response) {
console.log(response.data)
if (typeof response.data === 'object') {
return response.data;
} else {
// invalid response
return $q.reject(response.data);
}
}, function (response) {
// something went wrong
return $q.reject(response.data);
});
Controller:
tags.forEach(function(tag) {
counter++;
data.saveTags(tag).then(function(data) {
console.log(data)
});
});
Error:
You need to return a promise from the function that is resolved or rejected when the $http POST request is finished.
It looks like instead you're trying to return the reject and resolve products from the $http function itself, while your saveTags function ends up returning nothing to its caller (ie. from your forEach loop).
Try this:
this.saveTags = function (tag) {
var deferred = $q.defer();
$http({
method: "POST",
url: '/api/projects/' + data.Id + '/tags',
data: ({ Name: tag.Name })
}).then(function (response) {
console.log(response.data)
if (typeof response.data === 'object') {
deferred.resolve(response.data);
} else {
// invalid response
deferred.reject(response.data)
}
}, function (response) {
// something went wrong
deferred.reject(response.data)
});
return deferred.promise;
}

Categories

Resources