I come from an embedded C background, and am self-taught in AngularJs 1.x; I suspect that my question is a generic JS question, rather than AngularJs specific.
I find this sort of pattern repeating in my code:
$http.get(url)
.success(function(data, status, headers, config)
{
console.log('Success');
});
})
.error(function(data, status, headers, config)
{
console.error('Error !');
});
Being a C guy, I am not similar with lambdas, although I am comfortable with callbacks.
The code for the success and failure parts can be very large, and it makes the code seem messy - especially seeing all that code passed as a parameter.
I am guessing that the parameters to $http is/are (a) promise(s).
Is there some way that I can make the code more modular, and easier to read & maintain?
For instance, I imagine that I could declare some success/failure functions & invoke those, stemming like:
function it_succded(data, status, headers, config))
{
console.log('Success');
});
function it_failed(data, status, headers, config)
{
console.error('Error !');
});
$http.get(url)
.success(function(data, status, headers, config)
{
it_succded(data, status, headers, config))
});
})
.error(function(data, status, headers, config)
{
it_failed(data, status, headers, config)
});
});
Of course, I could just code it & see, but ask here because I want to learn, and hope for an explanation from someone who truly understands this, preferably someone who codes (Angular)Js professionally.
For using the promise interface, you should not use .success() or .error(): they are deprecated. Instead, use .then() and catch(). The parameters these callbacks receive are slightly different:
$http.get(url).then(function(response) {
console.log("Success", response.data);
}).catch(function(response) {
console.log("Error", response.status);
});
response is an object that has the expected properties:
data – {string|Object} – The response body transformed with the transform functions.
status – {number} – HTTP status code of the response.
headers – {function([headerName])} – Header getter function.
config – {Object} – The configuration object that was used to generate the request.
statusText – {string} – HTTP status text of the response.
xhrStatus – {string} – Status of the XMLHttpRequest (complete, error, timeout or abort).
You could indeed define the callback functions separately, and then your callback arguments can just be function references:
function it_succded(response) {
console.log("Success", response.data);
}
function it_failed(response) {
console.log("HTTP Error", response.status, response.statusText);
}
$http.get(url).then(it_succded).catch(it_failed);
You could define these functions as methods, e.g. on the $scope object:
$scope.it_succded = function (response) {
console.log("Success", response.data);
$scope.data = response.data;
}
$scope.it_failed = function (response) {
console.log("HTTP Error", response.status, response.statusText);
}
$http.get(url).then($scope.it_succded).catch($scope.it_failed);
Little note: Be aware that this will not be set when these callbacks are called by the Promise implementation. So either do not use this in them, or define them as arrow functions (this will then be whatever it is in the lexical context), bind the functions to a specific this object, or provide little wrapper callbacks:
.then(function(response) {
return $scope.it_succded(response);
})
Yes, you can.
$http({
your configs
})
.then(funtion(result){ // function on success
return result.data // this is important as AngularJS $http will bind the data from the server into this variable
},
function(errorResponse){
if(errorResponse.status === 401){
// your code for handling 401 errors
}
else if(errorResponse.status === 404){
// your code for handling 404 errors
}
else if(errorResponse.status === 500){
// your code for handling 500 errors
}
})
.then(function(data){
// another function to process data
return data
})
.then(function(data){
// yet another function to process data
return data
})
We can combine as many then callback, as we want.
you can read more about promised from this link: https://scotch.io/tutorials/javascript-promises-for-dummies
Hope it will help you.
Related
What is wrong with this code:
function mainCtrl($scope, $http) {
function loadData(){ $http.jsonp('http://www.pais.co.il/Lotto/Pages/last_Results.aspx?download=1')
.then(function (data, status, headers, config) {
console.log(data);
},
function (data, status, headers, config) {
console.log(data);
});
}
loadData();
};
I have a simple call to a URL.
I can see in my network that it was a success (200).
But my the response is beeing catch in the error function:
You aren't getting a 404 error. You are getting an invalid JS error.
You are making a JSONP request. The URL is returning CSV data, not JSONP.
You need to either:
Use a URL that returns JSONP
Use XMLHttpRequest instead of <script> (which is what $http.jsonp does behind the scenes) to load the data and ensure that the suitable Access-Control headers are set to give your JS permission to read the data
Fetch the data from your server instead
My server returns this kind of header: Content-Range:0-10/0:
I tried to read this header in angular with no luck:
var promise = $http.get(url, {
params: query
}).then(function(response) {
console.log(response.headers());
return response.data;
});
which just prints
Object {content-type: "application/json; charset=utf-8"}
Any ideas how to access the content range header?
Use the headers variable in success and error callbacks
From documentation.
$http.get('/someUrl').
success(function(data, status, headers, config) {
// this callback will be called asynchronously
// when the response is available
})
.error(function(data, status, headers, config) {
// called asynchronously if an error occurs
// or server returns response with an error status.
});
If you are on the same domain, you should be able to retrieve the response headers back. If cross-domain, you will need to add Access-Control-Expose-Headers header on the server.
Access-Control-Expose-Headers: content-type, cache, ...
Why not simply try this:
var promise = $http.get(url, {
params: query
}).then(function(response) {
console.log('Content-Range: ' + response.headers('Content-Range'));
return response.data;
});
Especially if you want to return the promise so it could be a part of a promises chain.
Updated based on Muhammad's answer...
$http.get('/someUrl').
success(function(data, status, headers, config) {
// this callback will be called asynchronously
// when the response is available
console.log(headers()['Content-Range']);
})
.error(function(data, status, headers, config) {
// called asynchronously if an error occurs
// or server returns response with an error status.
});
Additionally to Eugene Retunsky's answer, quoting from $http documentation regarding the response:
The response object has these properties:
data – {string|Object} – The response body transformed with the transform functions.
status – {number} – HTTP status code of the response.
headers – {function([headerName])} – Header getter function.
config – {Object} – The configuration object that was used to generate the request.
statusText – {string} – HTTP status text of the response.
Please note that the argument callback order for $resource (v1.6) is not the same as above:
Success callback is called with (value (Object|Array), responseHeaders (Function), status (number), statusText (string)) arguments, where the value is the populated resource instance or collection object. The error callback is called with (httpResponse) argument.
response.headers();
will give you all the headers (defaulat & customs). worked for me !!
Note . I tested on the same domain only. We may need to add Access-Control-Expose-Headers header on the server for cross domain.
The response headers in case of cors remain hidden.
You need to add in response headers to direct the Angular to expose headers to javascript.
// From server response headers :
header("Access-Control-Allow-Methods: GET, POST, OPTIONS");
header("Access-Control-Allow-Headers: Origin, X-Requested-With,
Content-Type, Accept, Authorization, X-Custom-header");
header("Access-Control-Expose-Headers: X-Custom-header");
header("X-Custom-header: $some data");
var data = res.headers.get('X-Custom-header');
Source : https://github.com/angular/angular/issues/5237
According the MDN custom headers are not exposed by default. The server admin need to expose them using "Access-Control-Expose-Headers" in the same fashion they deal with "access-control-allow-origin"
See this MDN link for confirmation
[https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Expose-Headers]
I'm learning Angular but having troubles to understand a point. I want to get data from my server. I used this code (from angular's doc) :
this.myFunction = new function()
{
var uri = this.getServerUri() + "Connexion/"+login+"/"+CryptoJS.MD5(pass);
var promises = $http.get(uri)
.success(function(data, status, headers, config) {
// this callback will be called asynchronously
// when the response is available
//alert("success:"+JSON.stringify(data));
return data;
}).
error(function(data, status, headers, config) {
// called asynchronously if an error occurs
// or server returns response with an error status.
return "";
});
return promises;
};
But I don't understand the behavior of the success function. When I call this function, it works fine, I get the server's answer but I get the full promises object (I have to write "promises.data" to get my data). Could you explain me why ? Because in the success function I tried to return only the data.
EDIT : I forgot to add my calling function :
var serverAccepted = this.MyFunction().then(function(promise) {
var objet = promise.data;
if(!_.isEmpty(objet))
{
userService.setUser(objet, true);
return "";
}
else return "Error while trying to login on the server";
});
return serverAccepted;
Thank you
A promise is a way of dealing with asynchronous requests without blocking. Instead of waiting for a slow operation (like waiting for the server to respond), before executing any more code, a promise is returned and code execution continues.
As such, when you call myFunction in your code above, it will return a promise object.
The promise then provides some neat methods for registering a callback to run code when the server does respond. These methods are .then() and catch() for the success and error case respectively.
Passing a function as an argument will call that function when the server has responded, and you can do with the data what you like. Take this example:
console.log("About to send request");
myFunction()
.then( function (myData){
console.log("Response received", myData);
})
.catch( function (myError){
console.error("Oh dear", myError);
});
console.log("Request sent!");
This will log the following to the console:
About to send request
Request sent!
Response received, {}
EDIT
In response to your comment:
why does the response received contains the full promise object and not only the data?
Because I return the data in the success function.
The response you receive does not contain a promise at all, but your function returns a promise. In your example serverAccepted is assigned the return value of myFunction() which is a promise and will not change.
The line:
.success(function(data, status, headers, config) {
return data;
})
Doesn't really do anything at all - returning a value from a .then() or .success() function is only useful if you are going to chain several calls to .then() together - it provides a way of manipulating values without an ever increasing pyramid of nested callbacks. Returning a value from within a promise callback (function passed to .then() or .success()) will not change the promise itself (serverAccepted in your case).
For example:
assume http GET some/url on your server returns:
{
foo: 'bar',
dorks: 12
}
When we use $http.get This data is inside the response object as a field called data. Most of the time we don't care much about the rest of the stuff, if the call was successful, we just want the data, so we can return it from the first .then() call (normally inside your service) so that any subsequent .then() calls (e.g. inside a controller) can access the data object directly.
myPromise = $http.get('some/url')
.then( function (response) {
console.log(response); //--> { status: 200, data: { foo:....}}
return response.data;
})
.then( function (data) {
console.log(data); //--> {foo: 'bar', dorks: 12}
return dorks;
})
.then( function (data) {
console.log(data); //--> 12
})
console.log(myPromise); //--> promise object with .then() .catch() etc
// ^ THIS WILL NOT CHANGE.
When I call this function, it works fine, I get the server's answer but I get the full promises object
Sounds like you are doing something like this:
var data = service.myFunction();
Above would be fine in synchronous world, however in case of asynchronous operations this is not correct usage of the promises. Instead you should use returned promise then/success methods:
service.myFunction().then(function(data) {
console.log(data);
});
$http request ,returns a promise with two $http specific methods: success and error. success function waits until request is to be finish with success (that means after $http promise resolves), and error function will execute only after the $http promise is rejected.
I have within my code that makes an $http.post to a back end, and of course things can go wrong. If I use the below syntax within angular:
$http.post(url).success(function(data, status, headers, config))
.error(function(data, status, headers, config));
If there is an error, I can retrieve the error code using status and handle it within the function defined in error(). However, if instead I take this approach (which is the one I am in fact using):
var deferred = $q.defer();
$http.post(url).success(deferred.resolve).error(deferred.reject);
return deferred.promise;
With the second syntax, I have all of my ajax calls within a separate ServiceJS directory, and can handle the successes or errors on a case by case basis. For instance, if the second snippet was Service.MyFunction() then in the code where I need it I would:
Service.MyFunction().then(function() {},
function(data, status, headers, config) {});
However, if I use this code block, status, headers, config are all undefined. My question is how can I retain this syntax but still retrieve the error code?
As an added reference, the back end is a C# Web API project, which would return errors using return BadRequest(); or the like.
Try something like this:
myFunction(){
var deferred = $q.defer();
// you can use .then() instead of .success or .error
$http.post(url).then(function(successResponse){
var data = successResponse.data;
var status = successResponse.status;
...
deferred.resolve(successResponse);
}, function(failureResponse){
var status = failureResponse.status;
var config = failureResponse.config;
...
deferred.reject(failureResponse);
});
return deferred.promise;
}
well, I'd say it's a good practice and more standard to implement a http interceptor and handle the HTTP errors from there intead of handling the error one by one on each http or resource object, and your code will be located in a single location.
basically, you can segment the actions to take depending on the error status you get for example:
angular.module('app').factory('myInterceptor', ['$q',
function($q){
return {
'responseError': function(rejection){
switch(rejection.status){
case 0:
//'No connection, is the internet down?'
break;
case 422:
// error
break;
case 484:
// error
break;
default:
// message rejection.status, rejection.statusText
break;
}
return $q.reject(rejection);
}
};
}
]);
$http is already returning a promise, so why not use that one?
function myFunction() {
return $http.post(url);
}
// ...
myFunction().success(function(data, status, headers, config) {
// ...
});
... or ...
myFunction().then(...);
The promises returned from $http have the methods success and error in addition to the other promise methods. Read more at Angular docs for $http.
Im having an issue with the $scope.items=data when calling success. I am using this jsfiddle : http://jsfiddle.net/SAWsA/11/ However instead of hardcoded items I did this:
$scope.items=$http({method: 'GET', url: '/test/database/two'}).
success(function(data, status, headers, config) {
return data;
}).
error(function(data, status, headers, config) {
$scope.status=status;
});
and tried this:
$http({method: 'GET', url: '/test/database/two'}).
success(function(data, status, headers, config) {
$scope.items=data;
}).
error(function(data, status, headers, config) {
$scope.status=status;
});
When I put an alert within the success function I see the lenght being 25, so I know i am getting the data. However when I check the $scope.items after this $http run, I get a lengh of undefined after I leave the success function. Like it sets itself and loses it set outside of scope? Any help much appreciated.
When $http runs it will immediately return either [] or {} depending on whether isArray is set or not. The functions you pass to success or error are executed at a later time, when the data is received. When this data is received the [] or {} you had earlier will be populated with the data.
It sounds like you are running $http and testing for the data before it has had chance to be retrieved from the server. If you want to use the data then your relevant code should probably be inside the success function to defer the work until you have the data you want to work with..