How can I resolve a request in an AngularJS $httpProvider interceptor? - javascript

I hope that I'm missing something here as this seems like it should be pretty straightforward.
I'd like to create an $httpProvider interceptor to take a look at outgoing $http requests and for some (in this case, ones that hit non existent endpoints that I'd like to fake responses for) NOT make the request but resolve it instead with some fake response data.
Here's where I am at right now:
myApp.factory('mockedAPIResponses', ['$q', function($q) {
return {
request: function(config) {
if (/check for endpoint here/.test(config.url)) {
// Resolve request such that it actually makes a request to the server
var defer = $q.defer(config.timeout),
resolvedRequest = defer.resolve({ fakeResponseData : 'foo'});
return resolvedRequest;
}
return config;
}
}
}]);

A few options
1. Handle a response error:
Instead of an object with a ´request` function you should create a factory that returns a response handler in your case, since you are only interested in failed responses, you should implement and return an object with the responseError method:
function factory($q) {
return {
responseError: function(response){
if (response.status === 404) { l
// handle response
return $q.resolve(response)
}
return $q.reject(response);
}
}
}
If you are unable to handle the response you should reject it so other handlers (if any) along the chain have a chance to process it
2. Abort the request and handle the error:
You can abort the request in the request() method and then handle the aborted request in the responseError() method. Add some custom property or code if you want to distinguish it from regular response errors:
function factory($q) {
return {
request: function(config) {
if (/bad/.test(config.url)) {
config.statusText = 'Non existing path';
config.status = 404;
return $q.reject(config);
}
return config;
},
responseError: function(response) {
if (response.status === 404) {
// Handle not found errors
response.status = 200;
response.statusText = 'OK';
response.data = {fake: 'data'}
return $q.resolve(response);
}
return $q.reject(response);
}
}
Here is plunk that first checks the request and if it isn't valid it aborts it. Then it is handled by the responseError() method. You can check the network tab that no actual request is made.
3. Using cache:
Yet another solution (and maybe the easiest) is to redirect the request to a cached route:
function factory($cacheFactory, $q) {
var cache = $cacheFactory('fakePages');
cache.put('cachedUrl', {fake: 'data'});
return {
request: function(config) {
if (/bad/.test(config.url)) {
config.url = 'cachedUrl';
config.cache = cache;
}
return config;
}
}
}
Here is a plunk with the cache implementation

Related

Javascript: Promise returning instantly, rather than waiting for asynchronous process to finish

Essentially when i call my function getToken() it should return the bearer + token from the api.
The problem I have is that due to the asynchronous process that happens, the data is not returned instantly; so in reading the following resource:
How do I return the response from an asynchronous call?
My understanding is that I need to return my response in the form of a promise, and set a timeout to ensure that the return accounts for the time it takes for the server to send back my request in the form of a response.
var request = require('request-promise');
var time = require('timers');
class Auth {
getToken() {
let options = {
method: 'POST',
uri: 'https://example.com/service/ep',
body: {
username: 'someUser',
password: 'somePass'
},
json: true
}
request(options)
.then(function (body) {
// console.log(body)
return new Promise((resolve) => {
time.setTimeout(() => {
resolve(body)
},3000)
});
})
.catch(function (err) {
return err
});
}
}
module.exports = new Auth
Unfortunately when i run my program in the node repel, it returns nothing and it does not appear to wait; of course when i log my response 'console.log(body)', it appears meaning there must be something wrong with how i'm returning my promise; i'm quite new to the likes of promises.
Could use with a second pair of eyes.
My understanding is that I need to return my response in the form of a promise, and set a timeout to ensure that the return accounts for the time it takes for the server to send back my request in the form of a response.
No. You need to return a promise (request already gives you one) and then the code you return the promise to needs to expect a promise (and call then() on it to get the data).
You don't need any time delays.
var request = require('request-promise');
var time = require('timers');
class Auth {
getToken() {
let options = {
method: 'POST',
uri: 'https://example.com/service/ep',
body: {
username: 'someUser',
password: 'somePass'
},
json: true
}
return request(options);
}
}
module.exports = new Auth
const auth = require("Auth");
auth.getToken().then(function (data) {
console.log(data);
});

$http simultaneous duplicate API call, return promise

In my app, we are using micro services structure, and for that we have aso added caching.
But there is a scenario in which multiple same request gets fired, and the request are fired at the same time, so caching also doesn't seem to work.
I want that for duplicate request it should send promise of previous request.
Also there are lots of services, so adding promise to each service will not work. Is there any place where to handle duplicate request and pass the previous request promise at a common place. might be by using decorator or http interceptor.
Service call
testService.getTestByIds = function (
testIds,
contextInfo) {
var params = {
testId: requestDefinitionIds
};
params = CommonProvider.apppendContextParams(contextInfo, params,testInfoCache);
return $http.get(SERVICE_URL + 'tests', {
cache: testInfoCache,
params: params,
transformResponse: CommonProvider.appendTransformer($http.defaults.transformResponse, function (value) {
return transformTestResponse(value);
})
});
};
I assume you mean caching on the client, you could try something like this:
const activeRequest =
() => {
const requests = {};
return (url,params) => {
const promise = requests[url+JSON.stringify(params)];
if(promise){
return promise;
}
return requests[url+JSON.stringify(params)] =
//not sure what you use for angular, maybe $http for angular1
fetch(url,params)
.then(
x=>{
requests[url+JSON.stringify(params)] = undefined;
return x;
}
)
}
}

Angular HTTP Interceptor not hitting error functions

So I have pulled the interceptor straight from the angular HTTP documentation and yet this still doesn't work. The "request" and "response" functions get called ,but never the "requestError" or the "responseError".
myApp.config(['$httpProvider', function ($httpProvider) {
$httpProvider.interceptors.push(function ($q) {
return {
'request': function (config) {
return config; //gets called
},
'requestError': function (rejection) {
return $q.reject(rejection); //Never gets called
},
'response': function (response) {
return response; //gets called
},
'responseError': function (rejection) {
return $q.reject(rejection); //Never gets called
}
};
});
}]);
On the server I am returning a 400, but really any error would do. And here is the service
User.publicProfileGetProfile = function (value, type) {
return $http({
url: '/public/profile/' + type + '/' + value,
method: 'GET'
}).then(function (response) {
return response;
}, function(error){
return error;
});
};
No error functions are being called and every response goes through the response function. The standard angular error is displayed with the Bad Request (400) as usual. When the 400 error is returned, it is simply 'undefined' through the 'response' function in the interceptor.
Let me know if I've forgotten to include any important information.
By using return, the error handler is converting the rejection to a success. Instead use throw to chain the rejection.
User.publicProfileGetProfile = function (value, type) {
return $http({
url: '/public/profile/' + type + '/' + value,
method: 'GET'
}).then(function onSuccess(response) {
return response;
}, function onReject(error){
//return converts rejection to success
//return error;
//use throw to chain rejection
throw error;
});
};
When I saw that the JSFiddle (from #georgeawg) was working properly, I made sure mine looked exactly the same. When it didn't work, I looked around to see if I had any other interceptors that might cause problems. I had another interceptor that was being hit first and returning any errors as responses, then they would go through this one and it would process it as a successful response. I removed it and everything seems to be working correct now!

$http/$q/promise Questions (in Angular)

I can't seem to wrap my head around when $q/$http should trigger the onReject block.
Let's say I have a basic call:
$http.get('/users')
.then(function(res) {
return res.data;
}, function(err) {
console.log(err);
});
If I get a 500 Internal Server Error I'm going to end up in the onSuccess block. From my meager understanding of promises I guess this seems correct because I technically did get a response? The problem is an onSuccess block seems like the wrong place to have a bunch of
if(res.status >= 400) {
return $q.reject('something went wrong: ' + res.status);
}
Just so that my onReject block will get run. Is this the way it's supposed to work? Do most people handle 400+ statuses in the onSuccess block or do they return a rejected promise to force the onReject block? Am I missing a better way to handle this?
I tried doing this in an httpInterceptor but I couldn't find a way to return a rejected promise from here.
this.responseError = function(res) {
if(res.status >= 400) {
// do something
}
return res;
};
Your success-block will not be hit. Try this and see that error-block will hit if error code > 300.
$http.get('/users').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.
});
You could handle these in an interceptor instead of in your callback.
In your app.config you can configure your $httpProvider to something like this:
$httpProvider.interceptors.push(['$q', function ($q) {
return {
request: function (config) {
//do something
return config;
},
responseError: function (response) {
if (response.status === 401) {
//handle 401
}
return $q.reject(response);
}
};
}]);

Angular ui-router not injecting the resolve into controller

I'm just getting started with Angular and Express and facing tough times with it. I come from a java background and want to learn Angular and Express and therefore trying out to build one small application.
What I'm trying to do: I have given a password reset link to user so as to change his password. The link is something like:
localhost:9000/reset/:token
Now, I have created a simple view which shows an input box to change his password if token is valid otherwise prints an error if token is invalid based on ng-show property of angular.
Problem: Before I can render my above created view, I want ui-router to check if the :token is valid or not. I will be using the information of validity of token in my controller to control ng-show property mentioned above.
After reading this I tried to leverage the $stateProvider.state functionality with a resolve so as to get the response of validation of token as pre-requisite. This will help me when rendering the actual view where I'm using ng-show technique to show error message or input box to change the password based on the ui-router resolve promiseObject.
What is the issue now ?
Well, after breaking my head for too long, I decided to post my question over here. Can anyone please help me here ?
My questions:
1. I'm able to get the data/err from the api call but somehow ui-router is not injecting it in my controller. Can anyone tell me am I doing anything wrong here ?
2. Right now if the token is not valid, I'm returning a 404 in response from my backend api. But the factory method in frontend takes it as err (Is this expected in Node.js ?) and the err is thrown which results in deferred.reject(). Now, if I go with ui-router definition, if the promise is not resolved then the view won't be rendered, right ? Is there any way by which I can pass this err also to my controller ? Reason why I'm asking to pass err is, my view's ng-show logic is based on the response code (4xx/2xx) depending on which I'll show error message or input box.
Code snippets:
Factory Method which calls the rest api:
isPasswordResetTokenValid: function(token, callback) {
var cb = callback || angular.noop;
var deferred = $q.defer();
return User.getUserByPasswordResetToken(token,
function(data) {
deferred.resolve(data);
return cb(data);
},
function(err) {
deferred.reject(err);
return cb(err);
}.bind(this)).$promise;
}
'use strict';
angular.module('scrubApp')
.config(function ($stateProvider) {
$stateProvider
.state('passwordreset', {
url: '/reset/:token',
templateUrl: 'app/account/passwordreset/passwordreset.html',
resolve: {
promiseObj2: function($stateParams, Auth){
var token = $stateParams.token;
console.log(token);
var response = Auth.isPasswordResetTokenValid({token: token})
.then( function(response) {
console.log(response); // shows response
if(response.status == 404) {
//$scope.token-expiry = true;
return response;
}
if(response.status == 200) {
// $scope.user = response.data;
}
})
.catch( function(err) {
console.log(err); // shows error
return err;
});
}
},
controller: 'ResetPasswordCtrl'
});
});
ResetPasswordCtrl controller:
'use strict';
angular.module('myApp')
.controller('ResetPasswordCtrl', function ($scope, User, Auth, Mailer, $stateParams, promiseObj2) {
$scope.errors = {};
$scope.user = {};
console.log(promiseObj2); // This is coming undefined
$scope.isTokenExpired = promiseObj2; // Not able to inject promiseObj2
$scope.isFormSubmitted = false;
});
Thanks in advance
Your resolve promiseObj2 should return a promise from promise service, so that your controller will wait till promise gets resolved.
return Auth.isPasswordResetTokenValid({token: token})
Update
If you want to handle some logic on failure of your token request then you could handle it in your promise itself, that can do couple of thing like
You could redirected to other page using $state.go('login') or $state.go('error') page.
Code
promiseObj2: function($stateParams, Auth, $state){
var token = $stateParams.token;
console.log(token);
return Auth.isPasswordResetTokenValid({token: token}) //added return here
.then( function(response) {
console.log(response); // shows response
if(response.status == 404) {
$state.go('error')
}
if(response.status == 200) {
return response;
}
})
.catch( function(err) {
console.log(err); // shows error
return err;
});
}
If you want to show html page anyhow if error occurs then You could also return data from the .then of promiseObj2 object that will have information about error message. So that error information is return to the controller
Code
promiseObj2: function($stateParams, Auth, $state){
var token = $stateParams.token;
console.log(token);
return Auth.isPasswordResetTokenValid({token: token}) //added return here
.then( function(response) {
console.log(response); // shows response
if(response.status == 404) {
return {status: 404, data: "doen't found resource"}
}
if(response.status == 200) {
return response;
}
})
.catch( function(err) {
console.log(err); // shows error
return err;
});
}
Then inside controller we will get resolve the promise of promiseObj2 object and then you will get the value of error in the .then function of it.
angular.module('myApp')
.controller('ResetPasswordCtrl', function ($scope, User, Auth, Mailer, $stateParams, promiseObj2) {
$scope.errors = {};
$scope.user = {};
promiseObj2.then(function(resp){
console.log(resp)
$scope.isTokenExpired = resp.isTokenExpired;
}, function(err){
console.log(err)
})
});
Update
If we want to handle a condition where server return 4XX status that means our ajax will call catch function that won't return promise though. We could solve this case by creating custom promise using $q and we will resolve it from the promiseObj2
Code
promiseObj2: function($stateParams, Auth, $state, $q){
var token = $stateParams.token,
deffered = $q.defer();
console.log(token);
Auth.isPasswordResetTokenValid({token: token}) //added return here
.then( function(response) {
console.log(response); // shows response
if(response.status == 404) {
//return {status: 404, data: "doen't found resource"}
deffered.resolve({status: 404, data: "doen't found resource"});
}
if(response.status == 200) {
//return response;
deffered.resolve(response);
}
})
.catch( function(err) {
console.log(err); // shows error
deffered.resolve(err);
});
return deffered.promise;
}

Categories

Resources