Is there a way to add interceptor only specific to one particular request, like transformRespponse?
Doing like so will add global interceptor that will be executed on every request.
$httpProvider.interceptors.push(function($q) {
return {
'response': function(response) {
if (isBad(response)) {
return $q.reject(rejectionReason(response));
}
}
};
});
I think you over engineer the solution. $http already return promise with two callbacks
$http.get('your_url').then(onSuccess, onError)
function onSuccess (response) {
if (isBad(response)) {
return $q.reject(rejectionReason(response));
}
// do some magic
}
function onError(response){
return $q.reject(rejectionReason(response))
}
and really the isBad should probably happen on server side and return an http error so it's handled by onError
you can get request url inside response function
$httpProvider.interceptors.push(function($q) {
return {
'response': function(response) {
if(response.config.url == '/particular_request_url'){
//do something
}
return response
}
};
});
Related
I'm using the ngMockE2E to mock the httpBackend while developing the UI in Angular JS. The App runs on a Grizzly-Server with a backend which is provided by a virtual machine. Now when I go on the Website the Console logs the Error:
Unexpected request: GET /api/info
No more request expected
In my case I only want to mock the data of one database. Additional to that I want to turn the mock ON/OFF on the fly via a button. This was working until the Error comes up.
For that case I've written the following:
$httpBackend.whenPOST(function (url) {
if (!mockup) {
return false;
}
var target_url = (someUrl);
return target_url === url;
}).respond(function (method, url, data) {
return [200, [someData], {}, 'mockupData'];
}
);
Additional to that I've added the following to pass all the other requests:
// pass the rest of the queries
$httpBackend.whenGET(/.*/).passThrough();
$httpBackend.whenPOST(/.*/).passThrough();
$httpBackend.whenPUT(/.*/).passThrough();
$httpBackend.whenDELETE(/.*/).passThrough();
So for all of you who face the same problem. I found a workaround for the issue. But I'm sure there should be another better solution.
For that I used an Interceptor which is also provided by angular. Here the api
So I wrote this:
app.factory('httpInterceptor', function () {
return {
'response': function (response) {
// therefore you can enable the mockup with a switch or a button
if (!mockup) {
return response;
}
try {
// in the config object is the requested url
if (response.config.url !== someUrl) {
// don't intercept; return response unchanged
return response;
}
// found an url which should be mocked
// generate or modify the data
var generatedData = generateSomeData();
response.data.push(generatedData)
return response;
} catch (TypeError) {
// don't intercept
return response;
}
}
}
}
);
app.config(['$httpProvider', function ($httpProvider) {
$httpProvider.interceptors.push('httpInterceptor');
}]);
We have APIs which returns errors inside the reponse XMLs instead of rejecting them and sending error responses.
So I have the following code to handle them,
$http({
url: apiURL,
method: "POST",
data: req,
headers: oHeaders,
dataType: "xml",
})
.success(function(data,status) {
console.log('success!!!!!');
deff.resolve(data);
})
.error(function(data,status) {
console.log('This is what I want to see!!!!!');
deff.reject(data);
});
myApp.factory('customHttpInterceptor', ['$q', function ($q) {
return function (promise) {
return promise.then(function (response) {
var parsed;
if (response.config.dataType == 'xml'){
parsed = $.xml2json(response.data);
if (parsed) {
angular.forEach(parsed, function(v, k) {
if (k == 'status') {
if (v.APIErrors) {
return $q.reject(response);
}
}
});
}
console.log(parsed);
}
return response;
}, function (errResponse) {
// do something on error
console.log('Error section in interceptor');
return $q.reject(errResponse);
});
};
}]);
What I expected to get from is that when an error is identified within the interceptor it will reject the promise and the control would get into the error section of the $http and log the "This is what I want to see!!!!!" message. But instead is show the "success!!!!!" message.
Is this the way it works or am I doing something wrong?
Ish
I see several things that seem wrong here :
Returning a result in forEach
angular.forEach(parsed, function(v, k) {
if (k == 'status') {
if (v.APIErrors) {
return $q.reject(response);
}
}
});
You probably wrote this because you're used to put return statements inside for loops. This does not work here, because you're returning from inside a function body. There is never any reason to return something in a forEach body function: this value is not going to be used by anything.
Angular Interceptor API
Maybe you just didn't show it in your code, but you have to register your interceptor.
$httpProvider.interceptors.push('customHttpInterceptor');
What's more, your interceptor does not have the correct form : in your case, it should be a object with a response method that returns a promise of a response from a response.
I'm trying to figure out if it is possible to use a $http interceptor to cancel a request before it even happens.
There is a button that triggers a request but if the user double-clicks it I do not want the same request to get triggered twice.
Now, I realize that there's several ways to solve this, and we do already have a working solution where we wrap $http in a service that keeps track of requests that are currently pending and simply ignores new requests with the same method, url and data.
Basically this is the behaviour I am trying to do with an interceptor:
factory('httpService', ['$http', function($http) {
var pendingCalls = {};
var createKey = function(url, data, method) {
return method + url + JSON.stringify(data);
};
var send = function(url, data, method) {
var key = createKey(url, data, method);
if (pendingCalls[key]) {
return pendingCalls[key];
}
var promise = $http({
method: method,
url: url,
data: data
});
pendingCalls[key] = promise;
promise.finally(function() {
delete pendingCalls[key];
});
return promise;
};
return {
post: function(url, data) {
return send(url, data, 'POST');
}
}
}])
When I look at the API for $http interceptors it does not seem to be a way to achieve this. I have access to the config object but that's about it.
Am I attempting to step outside the boundaries of what interceptors can be used for here or is there a way to do it?
according to $http documentation, you can return your own config from request interceptor.
try something like this:
config(function($httpProvider) {
var cache = {};
$httpProvider.interceptors.push(function() {
return {
response : function(config) {
var key = createKey(config);
var cached = cache[key];
return cached ? cached : cached[key];
}
}
});
}
Very old question, but I'll give a shot to handle this situation.
If I understood correctly, you are trying to:
1 - Start a request and register something to refer back to it;
2 - If another request takes place, to the same endpoint, you want to retrieve that first reference and drop the request in it.
This might be handled by a request timeout in the $http config object. On the interceptor, you can verify it there's one registered on the current request, if not, you can setup one, keep a reference to it and handle if afterwards:
function DropoutInterceptor($injector) {
var $q = $q || $injector.get('$q');
var dropouts = {};
return {
'request': function(config) {
// I'm using the request's URL here to make
// this reference, but this can be bad for
// some situations.
if (dropouts.hasOwnProperty(config.url)) {
// Drop the request
dropouts[config.url].resolve();
}
dropouts[config.url] = $q.defer();
// If the request already have one timeout
// defined, keep it, othwerwise, set up ours.
config.timeout = config.timeout || dropouts[config.url];
return config;
},
'requestError': function(reason) {
delete dropouts[reason.config.url];
return $q.reject(reason);
},
'response': function(response) {
delete dropouts[response.config.url];
return response;
},
'responseError': function(reason) {
delete dropouts[reason.config.url];
return $q.reject(reason);
}
};
}
Sometimes, the API I'm using will return 200 ok even though there has been an error. The response JSON object will look something like:
{
error: true
}
I've built a $http response interceptor that simply checks for this error and rejects it. I want it to then jump into my responseError function:
$httpProvider.interceptors.push(function($q) {
return {
response: function (response) {
if (response.data.error) {
// There has been an error, reject this response
return $q.reject(response);
}
return response;
},
responseError: function(rejection) {
// Display an error message to the user
...
return $q.reject(rejection);
}
}
});
Problem is, even after rejecting the response, my responseError function isn't called. It is called on 500 errors etc so I know it's working. I'd expect a rejection to do the same thing.
From the docs:
responseError: interceptor gets called when a previous interceptor threw an error or resolved with a rejection.
Any ideas on what's missing?
Looks like this isn't possible to do. To cut down on duplicate code, simply declare the error handling function separately and reuse it inside the response and responseError functions.
$httpProvider.interceptors.push(function($q) {
var handleError = function (rejection) { ... }
return {
response: function (response) {
if (response.data.error) {
return handleError(response);
}
return response;
},
responseError: handleError
}
});
To add to this answer: rejecting the promise in the response interceptor DOES do something.
Although one would expect it to call the responseError in first glance, this would not make a lot of sense: the request is fulfilled with succes.
But rejecting it in the response interceptor will make the caller of the promise go into error handling.
So when doing this
$http.get('some_url')
.then(succes)
.catch(err)
Rejecting the promise will call the catch function. So you don't have you proper generic error handling, but your promise IS rejected, and that's useful :-)
Should you want to pass the http response to the responseError handler, you could do it like this:
$httpProvider.interceptors.push(function($q) {
var self = {
response: function (response) {
if (response.data.error) {
return self.responseError(response);
}
return response;
},
responseError: function(response) {
// ... do things with the response
}
}
return self;
});
I have a "cancellable" angularJs $http call like this:
var defer = $q.defer()
$http.post("http://example.com/someUrl", {some: "data"}, {timeout: defer.promise})
And I cancel that ajax request using defer.resolve() because some logic requires it.
Some where else in my code, I have an iterceptor like this:
angular.module("services.interceptor", arguments).config(function($httpProvider) {
$httpProvider.interceptors.push(function($q) {
return {
responseError: function(rejection) {
if(rejection.status == 0) {
alert("check your connection");
}
return $q.reject(rejection);
}
};
});
});
});
Problem:
If there is an Internet connection problem, ajax fails with status 0 and interceptor catches it.
If ajax is cancelled by the timeout promise, than status is also 0 and interceptor catches it.
I can't find out if it is cancelled or got error in responseError handler.
My naive approach is to check if timeout is defined like this:
responseError: function(rejection) {
if(rejection.status == 0 && !rejection.config.timeout) {
alert("check your connection");
}
return $q.reject(rejection);
}
It only guarantees that, there is a timeout condition on that request, not it failed because of it. But it is better than nothing.
Are there a really working way of determining if ajax is failed or cancelled?
I'm using AngularJs 1.1.5
I ended up doing this using a flag, which is checked within the error handler.
I started off following the advice at the end of this AngularJS Github issue: https://github.com/angular/angular.js/issues/1159#issuecomment-25735438
This led me to build a service which used a promise to manually "timeout" the http call - as you have done below - by copying the linked plunk from that issue: http://plnkr.co/edit/P8wKns51GVqw5mwS5l5R?p=preview
But this wasn't quite complete, as this didn't address the issue you raised; in the error handling, how to determine if the call was a genuine server outage / timeout, or simply a manually triggered timeout. In the end, I had to resort to storing it in another variable (similar to the timeout promise), which can be seen in this plunk: http://plnkr.co/edit/BW6Zwu
The main method of this code looks as follows
return function (url) {
var cancelQuery = null;
var queryCancelled = false;
return function runQuery(query) {
if (cancelQuery) {
queryCancelled = true;
cancelQuery.resolve();
}
cancelQuery = $q.defer();
return $http.
get(url, { params: { query: query }, timeout: cancelQuery.promise }).
then(function (response) {
cancelQuery = null;
return response.data;
}, function (error) {
if(queryCancelled) {
console.log("Request cancelled");
queryCancelled = false;
} else {
console.log("Actual Error !");
}
});
};
};
Not too elegant, but it seems to work and avoids any nasty race conditions from what I have observed.
If you abort request using timeout, the timeout is listed in config of response.
Check for .config.timeout in your response object:
function config($httpProvider) {
$httpProvider.interceptors.push(function ($q, $injector) {
return {
request: function (config) {
return config;
},
response: function (response) {
return response || $q.when(response);
},
responseError: function (response) {
switch (response.status) {
case -1:
case 500 :
case 501 :
case 502 :
case 503 :
case 504 :
// the webapp sometimes aborts network requests. Those that dropped should not be restarted
if (response.status === -1 && response.config.timeout) {
break;
}
return retryRequest(response.config, response.status);
}
// otherwise
return $q.reject(response);
}
};
});
}
I know the question is already a little bit older, but I came upon it looking for a solution for the same problem. Other than in the answer already given I don't want to check a flag which I set at the same time as i cancle the AJAX request. It could already be a new request, which wasn't cancled.
So I found a different solution for me. Perhaps someone can make use of it, althoug it isn't an up to date question.
The quintessence of my solution is, to check not only the status. If the data is also null, it was a cancled request.
var defer = $q.defer()
$http.get("http://example.com/someUrl", {timeout: defer.promise})
.error(function(data, status){
if (data === null && status === 0) {
// the request was cancled
}
});
You can use (rejection.config.timeout.status != 499):
...timeout.promise.status = 499;
...timeout.resolve();
responseError: function(rejection) {
if(rejection.status == 0 && rejection.config.timeout.status != 499) {
alert("check your connection");
}
return $q.reject(rejection);
}
instead of:
responseError: function(rejection) {
if(rejection.status == 0 && !rejection.config.timeout) {
alert("check your connection");
}
return $q.reject(rejection);
}
By the way in this case you don't need additional request decorator.
I was able to determine if the request was cancelled in Chrome v55 by checking the global event object's property type against the string "abort" (code below). I had the problem that ongoing requests were cancelled when navigating to another page and triggered the responseError.
angular.module("services.interceptor", arguments)
.config(function($httpProvider) {
$httpProvider.interceptors.push(function($q, $window) {
return {
responseError: function(rejection) {
if($window.event.type === "abort") {
// handle cancelled here
} else if(rejection.status < 1) {
alert("check your connection");
}
return $q.reject(rejection);
}
};
});
});
});
(also: The status property seems to have changed from 0 to -1 for network errors, so I wrote rejection.status < 1))