Cancel / Abort all pending requests in angularJs - javascript

On route change, I need to abort ALL pending requests from previous route so that I don't run into problems of responses from previous route messing up data on my current route (it happens sometimes when responses from previous route take long time to finish).
I have thought about using http interceptor for this:
$httpProvider.interceptors.push(function($q) {
return {
'request': function(config) {
},
'response': function(response) {
}
};
});
In the request function, I could modify the config.timeout with a promise as suggested here and store all the deferred objects in a global cache so that I could cancel all of them.
The problem with this approach is that it may override config.timeout set in other places in the code.
I think another solution could be to cancel all ajax requests at XMLHttpRequest level, but I don't know how to do it.
Any suggestions? Thanks.

As you say, timeout is the only API we have of use right now to cancel a running $http request. I think you're right on the money with an interceptor coupled with a cancel promise.
What you could do is attach the full deferred object on the $http request, and cancel all pendingRequests in your route change handler.
Something like this could (perhaps*) work?
angular.module('module').config(function ($httpProvider) {
$httpProvider.interceptors.push(function ($q) {
return {
request: function (config) {
if (!config.timeout) {
config.cancel = $q.defer();
config.timeout = config.cancel.promise;
}
return config;
}
}
});
});
angular.module('module').run(function ($rootScope, $http) {
$rootScope.$on('$stateChangeStart', function () {
$http.pendingRequests.forEach(function (pendingReq) {
if (pendingReq.cancel) {
pendingReq.cancel.resolve('Cancel!');
}
});
});
});
*: I say perhaps, because I had success with this approach, but it's seldom you find a silver bullet to something like this.
edit
If you need to bypass the error handler of the cancelled promise, hook into responseError property and do manual labour there.
angular.module('module').config(function ($httpProvider) {
$httpProvider.interceptors.push(function ($q) {
return {
responseError: function (response) {
if (response.config.timeout.$$state.value === 'Cancel!') {
// debugger;
return $q.when('bypassed');
}
}
}
});
});
I'm starting to think that there is no generic/'cool' solution to reach the end result you desire. Treading some odd ground here : )
edit2:
Testing the above myself now. Returning $q.when('something') in the responseError will effectively bypass the error callback of the cancelled $http request. Let me know if it works out for you.

Related

Resolving a promise in Angular Service

We have a service, lets call it AccountService which exposes a method called getAccounts(customerId) among others.
In its implementation all it does is to fire up a $http GET request and return a promise to the calling controller which will put the returned array of accounts in the controller scope once resolved.
On a simplified view all looks like below:
// The service
.factory('AccountService', ['$http', function($http) {
var _getAccounts = function(customerId) {
var request = {
'method': 'GET',
'url': 'http://localhost:8081/accounts/' + customerId
};
return $(request);
};
return {
getAccounts: _getAccounts
};
}]);
// Inside the conntroller
AccountService.getAccounts($scope.customerId)
.then(function(response) {
$scope.accounts = response.data;
});
So once the promise will resolve the controller scope will get populated with the list of accounts.
Note that I kept the above code as simple as I could to get you the idea of what my problem is but in reality it will be code to deal with exceptions, watcher to refresh, etc. Everything works fine.
My problem is that this AccountService is used from lots of controllers and putting the promise resolve in all of these looks to me not only repeating all this boiler plate resolver code but also complicating the unit testing as I am obliged to r/test both successful and exception scenarios in every single controller test.
So my question is:
Is there a nice way to resolve the promise in the service and return the response to the controller, not the promise?
Please note I am a very beginner with Angular and JS so please be gentle if my question looks naive. I have heaps of java experience and my mind seems to go java like everywhere which may not be the case.
Thank you in advance for your inputs
To answer your original question:
Is there a nice way to resolve the promise in the service and return the response to the controller, not the promise?
In my opinion, no, there isn't. It boils down to the way asynchronous calls work - you either pass a callback (and the method returns nothing), or you don't pass a callback and the method returns an object which will be notified (a promise). There may be some workarounds, but I don't think it gets nicer than that.
One way to partially reduce the boilerplate is to use a catch in the service, and return the promise returned by it instead.
Consider the following extremely simplified example:
angular.module('myApp')
.factory('NetworkRequests', [
function() {
var _getData = function() {
var promise = new Promise((resolve, reject) => {
var a = true,
data = ['a', 'b', 'c'];
if (a) {
resolve(data);
} else {
reject('Rejection reason: ...');
}
});
return promise.catch((error) => {
// Notify some error handling service etc.
console.log(error);
return [];
});
};
return {
getData: _getData
};
}
]);
The promise variable would be the result from your http request. You should return some data in the catch function that makes sense in the controller context (e.g. empty array). Then you don't have to bother with error handling in the controller:
angular.module('myApp')
.controller('DataController', ['NetworkRequests',
function(NetworkRequests) {
NetworkRequests.getData().then((data) => {
this.data = data;
});
}
]);
Again, this doesn't solve the complete issue, but at least the error handling part can be encapsulated in the service.
You can design in such a way that once your $http is done with fetching the data, store it your factory variable (somewhat a cache), and for subsequent factory calls, you check if the cache has such data. If yes, return the cache data, else call the $http calls.
Here is the code:
.factory('AccountService', ['$http', '$q', function($http, $q) {
var cachedData = null;
var defered = $q.defer(); //create our own defered object
var _getAccounts = function(customerId) {
if (cachedData !== null) {
console.log('get from cachedData')
defered.resolve(cachedData); // resolve it so that the data is passed outside
return defered.promise; //return your own promise if cached data is found
} else {
var request = {
'method': 'GET',
'url': 'mockdata.json'
};
return $http(request).then((response) => { //return a normal $http promise if it is not.
console.log('get from $http');
cachedData = response.data;
return cachedData;
});
}
};
return {
getAccounts: _getAccounts
};
}]);
Here is the working plnkr. You can open up the console, and click the GetData button. You will see that first time it logs get from $http, where as subsequent calls it logs get from cachedData.
One way is to reuse an object and fill it with data. It is used by ngResource.
It is something like
var data = [];
function getAccounts(customerId) {
var promise = $http(...).then((response) => {
Object.assign(promise.data, response.data)
});
promise.data = [];
return promise;
};
Data is available for binding as $scope.accounts = AccountService.getAccounts(...).data. The obvious drawback is that there is a splash of unloaded content.
Another way is the one you've mentioned. It is being used most frequently. If there is a problem with WET code in controllers, it should be treated by eliminating WET code with class inheritance, not by changing the way it works.
Yet another way is the recommended one. Using a router and route/state resolvers eliminates the need for asynchronously loaded data. The data resolved in resolver is injected into route template as an array.

angularjs prevent error from bubbling to httpinterceptor

I have an http interceptor for my angularjs app that catches any http exceptions and handles them. There are a few cases where i would like to catch the error from the request and handle them there, preventing the error from bubbling to the interceptor. Does anyone know how this might be possible?
Here is the interceptor:
angular.module('x')
.factory('HttpErrorHandlerFactory', ['$q', '$rootScope', function ($q, $rootScope) {
return {
'responseError': function (rejection) {
if (rejection.status == 0) return $q.reject(rejection);
if (rejection.data === '')
rejection.data = 'Error';
$rootScope.Message.Value = rejection.data;
return $q.reject(rejection);
}
};
}]);
This is set at the app level, so any http requests that go through are caught by this. if they error, it catches them and displays the message.
Usually, we have calls like this:
someFactory.GetSomething(scope.id)
.success(function (result) {
//do something with result
}).error(function(error){
//MY QUESTION IS, HOW TO GET THIS ERROR TO NOT BUBBBLE UP TO THE INTERCEPTOR
});
To my best knowledge you cant really prevent the interceptor from catching the error before the caller function does. indeed that is annoying.
My solution was to add a config object to specific http requests (the ones i wanted to handle their error from the caller).
for example when using restangular:
myRestangularObject.get().withHttpConfig({handleByCaller: true})
and on my interceptor:
if (rejection.config.handleByCaller && rejection.config.handleByCaller === true) {
return $q.reject(rejection);
}
I know that this is a hack and doesn't answer your question but as i said, i don't think you can avoid the interceptor from being executed first.

Reading properties file value in Angular

I have gone through a few replies about using $http service for accessing the properties file, but now sure how it would fit in this scenario
I have created a service that returns the hostnames from the poperties file, the calling client to this service should make a blocking call to the service and proceed only if the property file is read.
var serviceMod = angular.module('serviceModule',[])
.factory('configService', function($http){
return {
getValue: function(key){
$http.get("js/resources/urls.properties").success(function(response){
console.log('how to send this response to clients sync??? ' + response)
})
return ????
}
}
})
someOtherControllr.js
var urlValue = configService.getValue('url')
The problem I am facing is to do with the aync nature of the $http service. By the time the response is received by the callback, the main thread is already finished executing the someOtherController.js
You need to resolve the promise returned by the service. We can just return the $http call and resolve it in our controller (since return $http.get be a promise itself). Check out the AngularJS $q and $http docs for a bettering understanding of the underlying mechanics going on, and observe the following change...
.factory('configService', function($http) {
return {
getValue: function(key) {
return $http.get('js/resources/urls.properties');
}
}
});
var urlValue;
// --asynchronous
configService.getValue('url').then(function(response) {
urlValue = response.data; // -- success logic
});
console.log('be mindful - I will execute before you get a response');
[...]
Simple way - use callback (it will still be async. In fact you cant make it sync) :
getValue: function(key, onSuccess){
$http.get("js/resources/urls.properties").success(function(response){
onSuccess(response);
})

AngularJS Service returning data instead of promise

I have a question conserning the use of services or factories inside an AngularJS application.
I Want to write a service that will return data from an Api which i can use inside my controllers.
Lets say i want to have a call like $scope.people = myPeopleService.getPeople();
and inside my service want to check if i already have people and return those otherwise i want to do a $http.get() and fill the people inside my service and then return them. I do not want to have .then inside my controller. Is this even possible?
If you dont want to return the promise to the consumer of your service:
.service('...', function ($http) {
var cachedResponse;
this.getStuff = function () {
if (cachedResponse) {
return cachedResponse;
}
return $http.get('someUrl')
.then(function (response) {
cachedResponse = response;
return response; // Return the response of your $http call.
});
};
});
My solution is to return a reference to an object that might be empty at first, but will hold the data eventually:
app.factory('myPeopleService', function($http){
var people = {};
$http
.post( "/api/PeopleService/getInitData", {
}).success(function (data, status, headers, config) {
angular.extend(people, data);
}).error(function (data, status, headers, config) {
});
return {
getPeople: function () {
return people;
}
};
});
the key is to use angular.extend in order to preserve the object reference.
in the controller you can use ng-show until the data is fulfilled, and/or use watch for processing
You won't be able to escape that at some level, somewhere, this call is asynchronous. One part of your question is easy, caching data is just an option in the $http call:
$http.get(url, {cache: true})
If you expose this call from a service only one call will be made over the network, no changes are required in your controller code.
Another thing to look into is using your router's resolve feature (ui-router and the vanilla router both support resolve). This can clean up some of the code in your controller.
It is not possible to do it without a .then in your use case. Since you want to make use of $http to fetch the people and return them (if they were not present already). $http works asynchronously and would return a promise. Due to the asynchronous behavior of $http it becomes mandatory to handle the promise returned in the calling function.
Hope this helps!

Issue with race condition betwen 2 Controllers in Angular.js with Ajax and a localStorage value

The value for myIdentity is created only once and is persistant saved in the local storage after first usage of the following Angular.js controller.
The function $scope.createIdentity() is a complex function in a more global controller and saves the result to $localstorage.myIdentity.
This perfectly works!!! the createIdentity() function works with Q promise inside for itself.
But in the AppCtrl I have an issue with race conditions because $localstorage.myIdentity is not yet resolved before
my $http XHR Request is fired. So it doesn’t contain any value for myId.
This occurs only the first time the controller is used.
But I need to start the socket.io connection at the first call of the AppCtrl and this is here an bigger problem for me.
At a second usage of AppCtrl the correct value for myIdentity is yet available in $localStorage.
TimeacleControllers.controller('StatusCtrl', ['$scope', '$http', 'Socket', '$localStorage',
function ($scope, $http, Socket, $localStorage) {
if ($localStorage.myIdentity === undefined) {
$scope.createIdentity();
}
var myParams = {
myId: $localStorage.myIdentity
};
$http
.post('http://example.org', myParams)
.success(function (data) {
console.log('received data: ' + data);
Socket.connect();
Socket.on('connected', function () {
console.log("Connection!");
});
Socket.on('message', function (msg) {
console.log("Message: " + msg);
});
})
.error(function (err) {
// Handle login errors here
console.log("Error - " + err);
});
}]);
So what can you do here to make the Ajax Request wait until the $localstorage.myIdentity can be resolved? Please help.
You mentioned about Q promise but having problem with async sequence...?
If createIdentity is returning a promise, just put the http call inside .then
$scope.createIdentity().then(function() {
$http.... // or you can wrap it inside a function
})
Edit: If you have no access to the code that populate the variable (normally happens inside directive), you can setup a one time watch to monitor the value change.
var unwatch = $scope.$watch(function(){
return $localStorage.myIdentity;
}, function(newValue){
if (newValue === undefined) return;
$http...
unwatch();
});
You said $scope.createIdentity() uses a promise (and presumably is an async operation). Make it return a promise for your code to observe. When it resolves, make it resolve the identity.
Promises are chainable. When you return a promise from then, the next then listens for that promise instead of the original promise. In this case, we make the next then listen to the AJAX.
$scope.createIdentity().then(function(identity){
// When createIdentity resolves, use the data for the AJAX.
// Return the AJAX promise for the next chained then.
return $http.get(...);
}).then(function(data){
// When the AJAX completes, use the data for the socket call
Socket.connect();
...
});

Categories

Resources