I am doing a http.post request and I am trying to display error message in case anything goes wrong. I am handling the error message in the service, then passing it to the controller and setting it in the $scope. I do not get any JS errors.... any ideas why that would be?
services.js
angular.module('app.services', [])
.factory('Model', function($http) {
Model.save = function(data) {
return $http.post(url, data)
.success(function(data, status, headers) {
console.log(data);
console.log(status);
console.log(headers);
})
.error(function(data, status, headers) {
var requestError = 'Something went wrong! :(';
return requestError; //pass error message back to $scope
});
}
return Model;
});
controllers.js
.controller('Ctrl', function($scope, Model) {
//form object data
$scope.formInfo = {};
//form save
$scope.saveData = function() {
//console.log($scope.formInfo);
$scope.requestError = '';
//form data
var data = {name: $scope.formInfo.name, description: $scope.formInfo.description, token: "/api/v1/tokens/1/"};
Model.save(data).then(function(requestError) {
alert(requestError);
if (requestError === '') {
//do nothing for now
}
else {
$scope.requestError = requestError;
}
});
};
})
A promise has two end states: resolved (success) or rejected (error).
In the controller, the then() function needs two different handlers if you want to access both states. The first handler only receives resolved (success) promises from the service. To also handle rejected (error) promises from the service, you'll need something like this:
Model.save(data).then(function(success) { ... }, function(error) { ... });
If you only care about the errors for some reason, use either of these (which are equivalent):
Model.save(data).then(null, function(error) { ... });
Model.save(data).catch(function(error) { ... });
In your service, make sure you're actually returned a rejected (error) promise back to the controller in the event of an error. You do this by using return $q.reject(requestError) instead of just return requestError.
Important note: Since you're using $http, this will already reject promises in the case of an HTTP error. If you don't handle rejected (error) promises in the service, they'll automatically be passed through to the controller's error handler. You may want to simply let $http's rejection pass through, and not have an error handler in the service. You can do this by simply removing .error() in the service. This allows the HTTP error to be handled by the controller directly. (This may or may not be desirable, depending on what kind of error handling you want to do).
The basic pattern I tend to follow with my HTTP services looks like this, which returns either a resolved promise with just the downloaded data, or returns the rejected promise (error) that comes out of $http directly to the controller:
return $http.get(config).then(function(success) { return success.data });
Related
I am making a http interceptor in angular and I want to make throw error from response of $httpProvider interceptor.
As per the documentation:
response: interceptors get called with http response object. The function is free to modify the response object or create a new one. The function needs to return the response object directly, or as a promise containing the response or a new response object.
responseError: interceptor gets called when a previous interceptor
threw an error or resolved with a rejection.
I want to do the bold part in above quote so that a response with status 200 (I've a condition there) is routed to responseError where I will handle the error.
Not returning a response throws following error:
Cannot read property 'data' of undefined
I do not want to return the response but want to pass it to next handler i.e responseError.
How can I do that?
I hope I made it clear.
Thanks.
Update (Code Below):
app.config(['$httpProvider', function($httpProvider) {
interceptor.$inject = ['$q', '$rootScope'];
$httpProvider.interceptors.push(interceptor);
function interceptor($q, $rootScope) {
return {
response: response,
responseError: responseError
};
function response(response) {
if (response.data.rs == "F") {
// I want to route it responseError -->
} else {
return response;
}
}
function responseError(response) {
// I want to handle that error here
}
}
}]);
Use:
return $q.reject(response);
Also make sure you return: (Read about it here)
return response || $q.when(response);
instead of:
return response;
In my Controller:
function login(credentials) {
AuthService
.login(credentials)
.then(successCallback, errorCallback);
//same issue with .then(successCallback).catch(errorCallback);
}
function successCallback() {
// do something after success
}
function errorCallback(data) {
// do something after error
}
and in my AuthService:
authService.login = function (credentials) {
return $http
.post(ENV.apiEndpoint + 'api/v1/login_check', credentials)
.then(
function (result) {
Session.create(result.data.token, result.data.data);
},
function (data) {
Messages.create('Login failed: ' + data.statusText);
}
);
}
When my POST delivers a 200 response code, everything works as expected do something after success is executed.
But when my POST results e.g. in a 401 I can see that Messages.create is called (so in this case it enters the error path), but unfortunately my Controller calls the successCallback and not the errorCallback.
I had to migrate this because I was using the deprecated and since Angular 1.6 removed .success and .error promise attributes. It was working back then, but after migration this doesn't work anymore.
What am I doing wrong here?
You may reject the promise in your error callback.
authService.login = function (credentials) {
return $http
.post(ENV.apiEndpoint + 'api/v1/login_check', credentials)
.then(
function (result) {
Session.create(result.data.token, result.data.data);
},
function (data) {
Messages.create('Login failed: ' + data.statusText);
return $q.reject(data);
}
);
}
From Angular $q doc:
reject(reason);
Creates a promise that is resolved as rejected with the specified
reason. This api should be used to forward rejection in a chain of
promises. If you are dealing with the last promise in a promise chain,
you don't need to worry about it.
When comparing deferreds/promises to the familiar behavior of
try/catch/throw, think of reject as the throw keyword in JavaScript.
This also means that if you "catch" an error via a promise error
callback and you want to forward the error to the promise derived from
the current promise, you have to "rethrow" the error by returning a
rejection constructed via reject.
I'm trying to make a request to a node function that will write to a file, but I'm not sure the best way to get the success or failure back to angular. The setup looks like this:
//controller (in an ng-click handler)
writeFileRoute.writeFile(file)
.then(function(response){
console.log('success', response);
}, function(error){
console.log('error', error);
});
//service
app.service('WriteFileService', function($http) {
this.writeFile = function(data) {
return $http.post('/writeFile', data)
.then(function(response) {
return {
'success': response
};
}, function(response) {
return {
'error': response
};
});
};
})
//server.js
app.post('/writeFile', function(req, res){
components.writefile(req.body, function(err){
//do something with error here?
});
//by this point I have become increasingly confused by what is going on
//node write script
var writefile = function(data, callback){
//data = JSON.stringify(data).message;
fs.writeFile('./testFile.txt', data.message, function(err){
callback(err);
});
};
module.exports = exports = writefile;
So the file is actually writing. I just don't know how to combine these separately working components into something that can notify angular of success or failure. I thought about using q, however I don't know if I should use angular $q or node Q. I would really like to use Q and/or $q, but I don't know which is the right solution or where to plug them in.
Edit (3 Feb)
This is the code that I am currently working with:
Angular:
angular.module('test', [])
.controller('ctrl', function($scope, WriteService){
$scope.testMessage = "test"
$scope.writeTheFile = function(){
WriteService.write()
.then(function(err){
if (err){
console.log(err);
}else{
console.log('no error');
}
});
}
})
.service('WriteService', function($http){
this.write = function(){
$http.get('./write/', function(response){
return {'success': response};
}, function(error){
return {'error': error};
});
};
});
Node write module
var writes = {
nfcall: function(data){
data = data.repeat(5);
return Q.nfcall(fs.writeFile, dest, data);
}
}
Node Server
app.get('/write/', function(req, res, next){
var data = 'some text string ' + +new Date() + '\n';
writeModule.nfcall(data)
.then(function(response){
return {'response': response};
})
.catch(function(err){
return {'index.js error': err};
});
});
Promises are a very good alternative to callbacks, and Q is an excellent Promise library to use on your node.js backend:
var Q = require('q');
Your server will look like something like this:
app.post('/writeFile', function(req, res, next) {
components.writefile(req.body)
.then(function() {
// Success! 'Writefile' worked fine, just send some 200 OK response.
res.send('OK');
})
.catch(function(err) {
// If 'Writefile' fails, this will be called!
// Just pass the error to the Error Handling middleware
next(err);
});
});
var writefile = function(data){
return Q.nfcall(fs.writefile, './testFile.txt', data.message);
};
module.exports = exports = writefile;
Note that nfcall() method is used for calling Node.js-style functions and getting back a promise.
Your components.writefile now returns a Promise, and what you do in app.post could be two things (have a look at the Promise Core Methods):
Catching any errors if your promise is rejected (catch method is called)
Istead, if the promise was resolved, then method is called.
I don't see anything wrong on your fronted, in fact, it already uses Promises: note that $http returns a Promise (have a look at it's Documentation).
In app.post, you must return something back to the client through res. Use res.send(data or error)
See: http://expressjs.com/en/api.html
This also mean you do not need a promise from the server to the client. The promise for writeFileRoute will be resolved with the res return.
I want two different controllers to run different functions after some promises are resolved in a service (i dont want this service to make an http request each time a controller needs the data, I only want one http request).
I have a service that makes a request and gets a promise. I want controller1 to see this resolution and then run some code. I then want controller2 to also see that this promise resolves and run some code (basically multiple then() methods that run on the same promise but from different files). How can I go about doing this?
All the examples I have seen have one controller running code after a certain promise resolves, but not multiple controllers listening for the same promise to resolve.
here is some code im borrowing from this article (ill add a 'mother controller' to illustrate my example, I dont want the son service to ever make his http call twice): http://andyshora.com/promises-angularjs-explained-as-cartoon.html
son service
app.factory('SonService', function ($http, $q) {
return {
getWeather: function() {
// the $http API is based on the deferred/promise APIs exposed by the $q service
// so it returns a promise for us by default
return $http.get('http://fishing-weather-api.com/sunday/afternoon')
.then(function(response) {
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);
});
}
};
});
father Controller:
// function somewhere in father-controller.js
var makePromiseWithSon = function() {
// This service's function returns a promise, but we'll deal with that shortly
SonService.getWeather()
// then() called when son gets back
.then(function(data) {
// promise fulfilled
if (data.forecast==='good') {
prepareFishingTrip();
} else {
prepareSundayRoastDinner();
}
}, function(error) {
// promise rejected, could log the error with: console.log('error', error);
prepareSundayRoastDinner();
});
};
Mother Controller:
var makePromiseWithSon = function() {
SonService.getWeather()
// then() called when son gets back
.then(function(data) {
// promise fulfilled
if (data.forecast==='good') {
workInTheGarden();
} else {
sweepTheHouse();
}
}, function(error) {
// promise rejected, could log the error with: console.log('error', error);
sweepTheHouse();
});
};
To have your factory service only get the url once, store the httpPromise in your factory service.
app.factory('SonService', function ($http) {
var weatherPromise;
function getWeather() {
return $http.get('http://fishing-weather-api.com/sunday/afternoon')
.then(function(response) {
if (typeof response.data === 'object') {
return response.data;
} else {
// invalid response
throw response;
}
}, function(response) {
// something went wrong
throw response;
});
}
function sonService() {
if (!weatherPromise) {
//save the httpPromise
weatherPromise = getWeather();
}
return weatherPromise;
}
return sonService;
});
The simple answer, in a non-angular-specific (but really easy to apply to Angular) way, is to create a service which caches ON-OUTBOUND-REQUEST (rather than caching return values, like most systems would).
function SearchService (fetch) {
var cache = { };
return {
getSpecificThing: function (uri) {
var cachedSearch = cache[uri];
if (!cachedSearch) {
cachedSearch = fetch(uri).then(prepareData);
cache[uri] = cachedSearch;
}
return cachedSearch;
}
};
}
function A (searchService) {
var a = this;
Object.assign(a, {
load: function ( ) {
searchService.getSpecificThing("/abc").then(a.init.bind(a));
},
init: function (data) { /* ... */ }
});
}
function B (searchService) {
var b = this;
Object.assign(b, {
load: function ( ) {
searchService.getSpecificThing("/abc").then(b.init.bind(b));
},
init: function (data) { /* ... */ }
});
}
var searchService = SearchService(fetch);
var a = new A(searchService);
var b = new B(searchService);
a.load().then(/* is initialized */);
b.load().then(/* is initialized */);
They're sharing the same promise, because the service they were talking to cached and returned the same promise.
If you wanted to be safe, you could cache a promise and then return new instances of promises which resolve (or reject) based on the cached promise.
// instead of
return cachedSearch;
// replace it with
return Promise.resolve(cachedSearch);
Each user is now getting a new instance, every time you make a request, but each instance is also passing or failing based on the original cached call.
And of course you can take it further, and put a time-limit on the cache, or have hooks to invalidate the cache, or whatever...
Converting this to Angular is also a snap
SearchService is a service
A and B are controllers
use $http instead of fetch (though fetch is really pretty)
in fetch( ).then(prepareData) you'd be converting data from JSON on success;
in $http, you'd be returning response.data because your users don't want to have to do that
either way, you're performing that operation exactly once, per outbound call, so cache it, too
use $q (and q methods) instead of native Promise
use angular.extend, instead of Object.assign
You're done; you've now ported that whole concept into Angular AND VanillaJS
I apologize if this isn't a good question, but it's something that's confusing me a bit.
I'm attempting to return specific data from an $http.post() from within a factory, however it would appear that $http always return the original promise. I'm looking to avoid .success and .error given their possible depreciation in v1.5. Given that the factory might do other things such as set items in localStorage etc, I do not want to return $http.post() directly.
Anyways, is the following the best way to return specific data from an angular $http promise?
function login (email, password) {
var deferred = $q.defer();
$http.post('/api/auth', {
email: email,
password: password
})
.then(function (data) {
return deferred.resolve('success');
})
.catch(function (data) {
return deferred.reject('fail');
});
return deferred.promise;
}
You don't need to create a deferred object. Instead, you can just return the result from $http.post. $http.post returns a promise that happens to have an extra two methods (success and failure).
function login(email, password) {
return $http.post('/api/auth', {
email: email,
password: password
})
.then(function (data) {
var newData = translateData(data);
//now the data will be passed to the next promise
return newData;
})
.catch(function (reason) {
/*do stuff with failure*/
//Now the rejection reason will be propagated to the next promise
return $q.reject(reason);
});
}
login()
//You should get your data here.
.then(function (data) { console.log(data); })
.catch(function (reason) { console.log(reason); });
You may be interested to read this blog post which explains how to propagate data and rejection reasons through a promise chain.
I would have written it with the error response as 2nd callback of the 'then' method (my example below). This way the error callback will only get called if there is an error with that $http request.
function login (email, password) {
var deferred = $q.defer();
$http.post('/api/auth', {
email: email,
password: password
})
.then(function (data) {
return deferred.resolve(data);
}, function (message) {
return deferred.reject(message);
});
return deferred.promise;
}
The way you have done it - using catch() - means it will get called if anything goes wrong in a promise chain. Hence, catch() would most likely be used at the end of several promises. For example, Something like this
CustomerService.login(email, password)
.then(getUserData)
.then(setUpAccount)
.catch($log.error);
See this great post, which explains it far better than I did
Also, check out the docs on promises, the section on 'The Promise API'