I'm trying to make promise inside a factory and then validate in locationChangeStart. The problem is that the locationChangeStart doesn't wait for my promise. What can I do to make my promise wait to complete?
Here is my code,
app.run(['$rootScope','$location','KeyFactory',
function($root, $location,KeyFactory) {
$root.$on('$locationChangeStart', function(event, curr, prev) {
KeyFactory.check();
console.log(KeyFactory.GetKeyPass()); ///PRINT undefined
if(KeyFactory.GetKeyPass()== true){
console.log('authorised');
}else{
$location.path('/login');
}
});
}]);
app.factory('KeyFactory', ['$http','$log', function($http,$log) {
var key = {};
key.setKeyPass = function(set) {
key.Status = set;
}
key.GetKeyPass = function() {
return key.Status;
}
key.check = function(){
$http.post('http://localhost/api/CheckPass').success(function(data) {
console.log(data);
key.setKeyPass(true);
}).error(function (data, status){
$log.error("error you cant acess here!");
console.log(status);
});
}
return key;
}]);
Asynchronous code doesn't work in synchronous way as you are thinking. After making an ajax it doesn't respond in the next line. In angular after making an ajax it return an promise object which is responsible to tell that response/error is going to happen.
There are couple of things missing in your code.
You should return a promise from the check method of service.
Then put .then function on check method promise & expect response in its success/error callback.
Code
key.check = function(){
return $http.post('http://localhost/api/CheckPass').then(function(response) {
var data = response.data;
key.setKeyPass(true);
}, function (response){
key.setKeyPass(false);
});
Run
KeyFactory.check().then(function(){
if(KeyFactory.GetKeyPass()== true){
console.log('authorised');
}else{
$location.path('/login');
}
});
Related
Given 2 JSON url, how do I make sure the code has finished retrieving the data from a.json, then only start retrieving the data from b.json, then only run init function?
var aUrl = "a.json";
var bUrl = "b.json";
My attempt:
var app = angular.module('calendarApp', []);
app.controller('ctrl', function($scope, $http) {
$http.get(aUrl).success(function(data) { });
$http.get(bUrl).success(function(data) {
init()}
);
var init = function(){}
I faced the same issue in my initial days.
There are many ways of doing it exactly as suggested here.
You need to know below two things before exploring:
1. JavaScript is synchronous
Synchronous Example[Flow in sequence]:
console.log('1')
console.log('2')
console.log('3')
It logs 1 2 3.
Example of making service calls
1. $http.get(aUrl).success(function(data) { console.log('1.any time response returns') });
2. $http.get(bUrl).success(function(data) { console.log('2.mummy returns')};
So as single-threaded javascript will first make a call to your below code with $http.get(aUrl) which hits the url and processes to fetch the data from the background.
$http.get(aUrl).success(function(data) { console.log('1.any time response returns') });
But the key thing to notice here is $http.get(aUrl) requested above doesn't wait until the data is returned in success/error. It moves to the next request $http.get(bUrl) and we just can't predict which response comes earlier.
$http.get(bUrl).success(function(data) { console.log('2.mummy returns') }
Output might be either
1.any time response returns
2.mummy returns
or
2.mummy returns
1.any time response returns
So, to overcome this situation we follow asynchronous operations in various ways.
2. Asynchronous Calls
$http.get(aUrl)
.then(function(response){
console.log('inside the first then response');
console.log(response.data);
//executing the second request after we get the first request
//and returns the **outputResponse** which is captured in the next **then** block
return $http.get(bUrl);
})
.then(function(**outputResponse** ){
console.log('outputResponse generated from to the second bUrl');
//you can call init() here
});
Above code suffices your requirement.
Click for more info using $q in future
Click here to know why to use then instead of success.
Might not be the best or cleanest method but quickly making your code do what you want it to do I got:
var app = angular.module('calendarApp', []);
app.controller('ctrl', function($scope, $http) {
$http.get(aUrl).success(function(data) {
$http.get(bUrl).success(function(data) {
init()
}
});
);
var init = function(){}
You could create a service layer in which define the two methods. Then inject the service into your controller:
//Controller
YourService.getUrl(urlA).then(function(response) {
if(response != null && response.success == true){
// do something
}
YourService.getUrl(urlB).then(function(response) {
if(response != null && response.success == true){
// do something
init()
}
},
function errorCallback(response) {
console.log("Error YourService: getUrlB ---> ");
});
},
function errorCallback(response) {
console.log("Error YourService: getUrlA ---> ");
});
// Example of method in your service
this.getUrl = function(urlA) {
try{
var deferred = $q.defer();
$http({
method: 'GET',
url: getUrlA,
params: {},
responseType: "json",
cache: false
})
.success(function(data, status, headers, config) {
deferred.resolve(data);
})
.error(function(data, status, headers, config) {
deferred.reject(data);
});
return deferred.promise;
}catch(e){
/* */
console.log("Service: getUrl ---> " + e);
}
}
$http.get returns a promise, so you can do:
return $http.get(aUrl)
.then(function(result) {
return $http.get(bUrl);
})
.then(function(result) {
return init();
},
function (error) {
// do something with the error
});
I suggest to use AngularJS promises. Mainly it has the benefit of loading the data asynchronly at the same time without having to wait until the first request is finished. see: https://docs.angularjs.org/api/ng/service/$q
var promises = [];
var loadingJson = function(url){
var defer = $q.defer();
$http.get(url).then(function(results){
defer.resolve(results);
}, function(err){
defer.reject(err);
});
return defer.promise;
};
promise.push(loadingJson('example.com/1.json'));
promise.push(loadingJson('example.com/2.json'));
$q.all(promises).then(function(resultList){
// Your hanadling here, resultList contains the results of both API calls.
}, function(errList){
// Your error handling here.
});
I've a bunch of functions which are nested due to top level function is a ajax request.
So i want to return a value instead of a promise in nested child function.
Parent
let getUserPermissions = function(id) {
let deferred = $q.defer();
let promise = accessRequestService.getPermissions(id);
promise.then(function(data) {
deferred.resolve(data);
}, function(err) {
deferred.reject(err);
})
return deferred.promise;
}
Child 1
$rootScope.userInit = function() {
return getUserPermissions(vzid)
.then(function(data) {
//Some code here
return data;
})
}
Child 2
let checkAuthorize = function(toState) {
return $rootScope.userInit().then(
function(data) {
//some code here
return data;
});
}
Level 3
checkAuthorize(toState).then( function(val){
$rootScope.isAuthorized = val;
if ($rootScope.isAuthorized == true) {
$log.info('is Authorized')
} else {
$log.info('is not Authorized');
throw new AuthorizationError()
}
})
At Level 3 we are still working with a promise. Can child 2 return a value instead of promise.
Expectation # Level 3
$rootScope.isAuthorized = checkAuthorize(toState);
if ($rootScope.isAuthorized == true) {
$log.info('is Authorized')
} else {
$log.info('is not Authorized');
throw new AuthorizationError()
}
The hard truth is: you can't, unless you want spaghetti code all around.
The best solution would be to use something like ui-router's resolve, getting all the permissions needed before the page is shown to the user. Then, you could use them on your controllers without any asynchronous calls.
You can use for it async/await construction. And use Babel for support old browsers.
Async
Await
function resolveAfter2Seconds(x) {
return new Promise(resolve => {
setTimeout(() => {
resolve(x);
}, 2000);
});
}
async function f1() {
var x = await resolveAfter2Seconds(10);
console.log(x); // 10
console.log('done');
}
f1();
Yes, this type of thing is possible, but it will change the behavior. You'll probably want to keep userInit, but you also add a userInitValue variable and initialize it as follows:
let userInitValue = null;
let userInit = function() {
return getUserPermissions()
.then(function(data) {
userInitValue = data;
return data;
})
}
So now userInitValue will start as null and then later be initialized to the relevant data.
function isKnownAuthorized(toDoSomething) {
// If we don't know whether the user is authorized
// because we are still waiting for the server to tell us
// then return false and disallow access for now
if(!userInitValue) return false;
// Otherwise return the truth
// (as of when we got the server response)
return userInitValue.isAuthorized(toDoSomething);
}
Note again the change in behavior. The price of getting an instant response, perhaps before the server gives you the data, is that the instant response could be wrong. So don't use this in a one-time :: expression in AngularJs.
Based on what you're hoping to achieve in Level 3, I'm guessing this function is going to be called multiple times with the same input. In this case, what I would do is make the call to the promise if there is not a cached result, and cache the result. This way you don't have to go down the promise chain, although I only count one promise in the code provided. There are multiple handlers on resolve, but only one promise.
You can run your code as if it was synchronous using nsynjs: it will evaluate code step-by-step, and if some function returns promise, it will pause execution, wait until promise is resolved, and assigns resolve result to data property. So, code below will be paused on level 1 until promise is resolved to actual value.
var getUserPermissions = function(id) {
return new Promise(function(resolve, reject) {
setTimeout(function(){
resolve({
id: id,
isAdmin: "yes he is",
})
}, 1000);
});
};
function synchronousCode() {
console.log("start");
var vzid = 35;
var userInit = function() {
return getUserPermissions(vzid).data;
};
var checkAuthorize = function() {
return userInit().isAdmin;
};
var isAuthorized = checkAuthorize();
console.log(isAuthorized);
};
nsynjs.run(synchronousCode, null, function(){
console.log("finish");
});
<script src="https://rawgit.com/amaksr/nsynjs/master/nsynjs.js"></script>
I'm using $state.transitionTo Method to be called before $stateChangeStart.
var transitionTo = $state.transitionTo;
$state.transitionTo = function(to, toParams, options) {
var from = $state.$current,
fromParams = $state.params;
to = to.name ? to : $state.get(to);
$rootScope.state = {
to: to.self,
toParams: toParams,
from: from.self,
fromParams: fromParams,
options: options
}
if (options.notify && options.notify !== false) {
return $q.reject(new AuthorizationError('Rejecting $state.transitionTo', 'Transition Rejected'));
} else {
return checkAuthorize(to).then(function(auth) {
$rootScope.isAuthorized = auth;
return transitionTo(to, toParams, options)
})
}
}
StateChangeStart
$rootScope.$on('$stateChangeStart', function(event, toState, toParams, fromState, fromParams) {
$log.info("Route change start from", fromState.url, "to", toState.url);
//event.preventDefault();
if ($rootScope.isAuthorized == true) {
$log.info('is Authorized')
//$state.go($rootScope.toState.name);
} else {
event.preventDefault();
$log.info('is not Authorized');
throw new AuthorizationError('User is not Authorized.', 'NOT_AUTHENTICATED')
}
});
I want to use the result of first api, into second api call. Scenario is like that, I want to use the result of first api, into second api call. If I am correct then I want synchronous api call(not sure). I tried to write following function but not working. function2 is call before function1. In function2 we are use result1 which is only come when function1 is called before function2, How I do.
$scope.function1 = function(){
var deferred= $q.defer();
$http.post(api1, data1)
.success(function(response, status) {
if (response.error == 0) {
console.log(response.result);
$scope.result1=response.result;
}
}) ;
deferred.resolve('Success') ;
return deferred.promise;
};
var promise = $scope.addDefaultValue();
promise.then(function(){
$scope.function2();
});
$scope.function2=function(){
var deferred = $q.defer();
$http.post(api2,result1)
.success(function(response, status){
if(response.error == 0){
}
});
deferred.resolve('Success') ;
return deferred.promise;
}
You could follow promise chain pattern here, follow chaining using .then on promise object.
No need to create extra overhead promise using $q, as $http methods returns promise object when they start an ajax.
Code
$scope.function1 = function() {
return $http.post(api1, data1)
.then(function(d) {
var response = d.data;
if (response.error == 0) {
console.log(response.result);
$scope.result1 = response.result;
}
return response;
});
};
$scope.function1().then(function(data) {
$scope.function2();
}, function(error) {
});
You cannot convert $http requests to "synchronous". That's not what "deferred" does. Deferred is a way to convert non-promise-capable functions to promise-capable functions. $http functions return promise objects so you don't need to use deferred.
$http.post(api, data1).then(function (response) {
$scope.result1 = response.data.result;
// return is important here
// so that you can keep chaining with .then
return $http.post(api2, response.data.result);
}).then(function (response) {
// here you have response from api2
$scope.result2 = response.data.result;
console.log(response.data);
}).catch(function (error) {
// here you can handle errors
// from either api calls
// second api call won't be made if the first one fails
console.log(error);
});
I have a global error handler for my angular app which is written as an $http interceptor, but I'd like to take it a step further. What I'd like is for each $http call that fails (is rejected), any "chained" consumers of the promise should first try to resolve the error, and if it is STILL unresolved (not caught), THEN I'd like the global error handler to take over.
Use case is, my global error handler shows a growl "alert box" at the top of the screen. But I have a couple of modals that pop up, and I handle the errors explicitly there, showing an error message in the modal itself. So, essentially, this modal controller should mark the rejected promise as "handled". But since the interceptor always seems to be the first to run on an $http error, I can't figure out a way to do it.
Here is my interceptor code:
angular.module("globalErrors", ['angular-growl', 'ngAnimate'])
.factory("myHttpInterceptor", ['$q', '$log', '$location', '$rootScope', 'growl', 'growlMessages',
function ($q, $log, $location, $rootScope, growl, growlMessages) {
var numLoading = 0;
return {
request: function (config) {
if (config.showLoader !== false) {
numLoading++;
$rootScope.loading = true;
}
return config || $q.when(config)
},
response: function (response) {
if (response.config.showLoader !== false) {
numLoading--;
$rootScope.loading = numLoading > 0;
}
if(growlMessages.getAllMessages().length) { // clear messages on next success XHR
growlMessages.destroyAllMessages();
}
return response || $q.when(response);
},
responseError: function (rejection) {
//$log.debug("error with status " + rejection.status + " and data: " + rejection.data['message']);
numLoading--;
$rootScope.loading = numLoading > 0;
switch (rejection.status) {
case 401:
document.location = "/auth/login";
growl.error("You are not logged in!");
break;
case 403:
growl.error("You don't have the right to do this: " + rejection.data);
break;
case 0:
growl.error("No connection, internet is down?");
break;
default:
if(!rejection.handled) {
if (rejection.data && rejection.data['message']) {
var mes = rejection.data['message'];
if (rejection.data.errors) {
for (var k in rejection.data.errors) {
mes += "<br/>" + rejection.data.errors[k];
}
}
growl.error("" + mes);
} else {
growl.error("There was an unknown error processing your request");
}
}
break;
}
return $q.reject(rejection);
}
};
}]).config(function ($provide, $httpProvider) {
return $httpProvider.interceptors.push('myHttpInterceptor');
})
This is rough code of how I'd expect the modal promise call to look like:
$http.get('/some/url').then(function(c) {
$uibModalInstance.close(c);
}, function(resp) {
if(resp.data.errors) {
$scope.errors = resp.data.errors;
resp.handled = true;
return resp;
}
});
1. Solution (hacky way)
You can do that by creating a service doing that for you. Because promises are chain-able and you basically mark a property handled at the controller level, you should pass this promise to your service and it'll take care of the unhandled errors.
myService.check(
$http.get('url/to/the/endpoint')
.then( succCallback, errorCallback)
);
2. Solution (preferred way)
Or the better solution would be to create a wrapper for $http and do something like this:
myhttp.get('url/to/the/endpoint', successCallback, failedCallback);
function successCallback(){ ... }
function failedCallback(resp){
//optional solution, you can even say resp.handled = true
myhttp.setAsHandled(resp);
//do not forget to reject here, otherwise the chained promise will be recognised as a resolved promise.
$q.reject(resp);
}
Here the myhttp service call will apply the given success and failed callbacks and then it can chain his own faild callback and check if the handled property is true or false.
The myhttp service implementation (updated, added setAsHandled function which is just optional but it's a nicer solution since it keeps everything in one place (the attribute 'handled' easily changeable and in one place):
function myhttp($http){
var service = this;
service.setAsHandled = setAsHandled;
service.get = get;
function setAsHandled(resp){
resp.handled = true;
}
function get(url, successHandler, failedHandler){
$http.get(url)
.then(successHandler, failedHandler)
.then(null, function(resp){
if(resp.handled !== true){
//your awesome popup message triggers here.
}
})
}
}
3. Solution
Same as #2 but less code needed to achieve the same:
myhttp.get('url/to/the/endpoint', successCallback, failedCallback);
function successCallback(){ ... }
function failedCallback(resp){
//if you provide a failedCallback, and you still want to have your popup, then you need your reject.
$q.reject(resp);
}
Other example:
//since you didn't provide failed callback, it'll treat as a non-handled promise, and you'll have your popup.
myhttp.get('url/to/the/endpoint', successCallback);
function successCallback(){ ... }
The myhttp service implementation:
function myhttp($http){
var service = this;
service.get = get;
function get(url, successHandler, failedHandler){
$http.get(url)
.then(successHandler, failedHandler)
.then(null, function(){
//your awesome popup message triggers here.
})
}
}
I have the following code in a service and I am calling fetchData function from the controller.
Service
app.service("geturl", function($http) {
urllist = [];
geturl.fetchData = function() {
var data = [];
for (i = 0; i < urllist.length; i++) {
(function(index) {
return $http.get(geturl.urllist[index], {
timeout: 8000
})
.then(function(response) {
data[index] = response.data;
});
}(i);
return data;
});
};
});
I want to write the success and error function of $http.get in the controller since it is needed in the UI, how can I go about it?
..I want to write the success and error function of $http.get in the
controller..
Usually, the .then() function takes two function arguments. The first argument is the success handler and the second as an error handler.
$http.get(url,options).then(function (response){
//success handler function
},function(error){
//error handler function
})
Alternatively, you can specify the .success and .error functions separately.
$http.get(url,options).success(function (response){
//success handler function
}).error(function(error){
//error handler function
})
UPDATE:
From your code, it seems that you intend to return something from your service geturl and providing the callbacks there itself. This is not how it is supposed to be done. You should return a promise from your service .
...
app.service("geturl",function($http){
...
getData:function(){
return $http.get(url,options);
}
...
});
...
and handle the success/error callbacks in the module where you are consuming the service
geturl.getData().success(function(){
//handle success
}).error(function(){
//handle error
})
In case you need to make multiple http requests, never ever use the for loop . Remember, everything is asynchronous and you are not guaranteed to get the response from one of the previous request before you make a new one. In such scenarios, you should use $q service. See #pankajparkar's answer for more details
Seems like you want to return a data after all the ajax gets completed, You could achieve this by using $q.all()
Service
app.service("geturl", function($http, $q) {
this.urllist = [];
this.fetchData = function() {
var data = [], promises = [];
for (i = 0; i < urllist.length; i++) {
(function(index) {
var promise = $http.get(geturl.urllist[index], {
timeout: 8000
})
.then(function(response) {
data[index] = response.data;
});
promises.push(promise); //creating promise array
}(i);
};
return $q.all(promises).then(function(resp){
return data; //returning data after all promises completed
});
};
});